• pyinstaller 自动更新版本


    自动更新版本流程

    1. 检测新版本,弹窗提醒更新
    2.下载更新文件压缩包
    3.将旧的配置文件移到新配置文件夹中
    4.关闭当前进程,移除旧文件夹里面的文件,将新文件移到当前位置
    5.启动新程序
    
    1. # -*- coding: utf-8 -*-
    2. import json
    3. import subprocess
    4. import sys
    5. import textwrap
    6. import threading
    7. # import webbrowser
    8. import zipfile
    9. from pathlib import os
    10. # import os
    11. import requests
    12. import shutil
    13. import semver
    14. import tkinter
    15. import tkinter.messagebox
    16. from tqdm.tk import tqdm
    17. from tkinter import Tk
    18. """
    19. pyinstaller -D unit_test.py -n '软件测试'
    20. """
    21. class Unit_Test_Version():
    22. def __init__(self, root=None):
    23. # 当前版本号
    24. self.current_version = '1.0.0'
    25. # 请求软件资源信息
    26. self.version_url = 'http://127.0.0.1:28082/search_version'
    27. # 软件名
    28. self.app_name = '软件测试'
    29. # 软件类型(同一个表存多个软件的版本)
    30. self.soft_type = 4
    31. self.root = root
    32. def upzip_file(self, zip_path=None, unzip_path=None):
    33. """
    34. :zip_path 压缩文件路径
    35. :unzip_path 解压文件路径
    36. :return 解压 zip 文件,返回所有解压文件夹下的路径
    37. """
    38. zip_file = zipfile.ZipFile(zip_path)
    39. if not os.path.isdir(unzip_path):
    40. os.mkdir(unzip_path)
    41. for names in zip_file.namelist():
    42. zip_file.extract(names, unzip_path)
    43. zip_file.close()
    44. return [os.path.join(unzip_path, i).replace('\\', '/') for i in zip_file.namelist()]
    45. def upzip_file_new(self, zip_path=None, unzip_path=None):
    46. paths = []
    47. if not os.path.exists(unzip_path):
    48. os.mkdir(unzip_path)
    49. with zipfile.ZipFile(file=zip_path, mode='r') as zf:
    50. for old_name in zf.namelist():
    51. file_size = zf.getinfo(old_name).file_size
    52. # 由于源码遇到中文是cp437方式,所以解码成gbk,windows即可正常
    53. new_name = old_name.encode('cp437').decode('gbk')
    54. # 拼接文件的保存路径
    55. new_path = os.path.join(unzip_path, new_name)
    56. paths.append(new_path)
    57. # 判断文件是文件夹还是文件
    58. if file_size > 0:
    59. # 是文件,通过open创建文件,写入数据
    60. with open(file=new_path, mode='wb') as f:
    61. # zf.read 是读取压缩包里的文件内容
    62. f.write(zf.read(old_name))
    63. else:
    64. # 是文件夹,就创建
    65. os.mkdir(new_path)
    66. return paths
    67. def get_version_info(self):
    68. # 获取版本信息
    69. try:
    70. data = json.dumps({'soft_type': self.soft_type})
    71. res = requests.post(self.version_url, data=data, timeout=15)
    72. response = json.loads(res.text)
    73. except:
    74. response = {}
    75. return response
    76. def del_current_pid(self):
    77. pid = os.getpid() # 关闭后台进程
    78. try:
    79. os.system('taskkill -f -pid %s' % pid)
    80. except:
    81. pass
    82. def check_version(self, version_info):
    83. self.root = Tk()
    84. self.root.withdraw() # 隐藏主窗口
    85. t = threading.Thread(target=lambda: self.check_for_update(version_info), name='update_thread')
    86. t.daemon = True # 守护为True,设置True线程会随着进程一同关闭
    87. t.start()
    88. self.root.mainloop()
    89. # def browser_update(self, ver, window):
    90. # webbrowser.open(ver.get('updateUrl'))
    91. # window.destroy()
    92. def check_for_update(self, version_info):
    93. version = version_info['version']
    94. ver = semver.compare(version, self.current_version)
    95. if ver:
    96. # 更新的内容
    97. publish_notes = version_info['remark']
    98. message = f'当前版本[{self.current_version}], 有新版本[{version}]\n' \
    99. f'更新内容:\n{publish_notes}\n请选择立即去下载更新[确定],暂不更新[取消]?'
    100. result = tkinter.messagebox.askokcancel(title='更新提示', message=message)
    101. if result:
    102. self.is_down_update = True
    103. self.down_zip(version_info)
    104. self.root.destroy()
    105. else:
    106. self.root.destroy()
    107. def down_zip(self, version_info):
    108. zip_url, file_name = version_info['zip_url'], version_info['file_name']
    109. res = requests.get(zip_url)
    110. # 当前文件夹路径
    111. # current_path = os.path.dirname(os.path.realpath(sys.argv[0]))
    112. # 当前文件夹上一级路径
    113. current_path = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
    114. zip_path = os.path.join(current_path, f'{zip_url.split("/")[-1]}')
    115. unzip_path = os.path.join(current_path, file_name+'_new')
    116. print('zip_path:', zip_path)
    117. print('unzip_path:', unzip_path)
    118. # with open(zip_path, 'wb') as f:
    119. # f.write(res.content)
    120. chunk_size = 1024 * 100
    121. total_size = int(res.headers.get('content-length', 0))
    122. progress_bar = tqdm(iterable=res.iter_content(chunk_size=chunk_size), tk_parent=None,
    123. leave=False, total=total_size, unit='B', unit_scale=True)
    124. # 设置下载进度条位置
    125. screenWidth = progress_bar._tk_window.winfo_screenwidth()
    126. screenHeight = progress_bar._tk_window.winfo_screenheight()
    127. progress_bar._tk_window.geometry(f"300x160+{screenWidth - 300}+{screenHeight - 400}")
    128. try:
    129. with open(zip_path, 'wb') as f:
    130. for chunk in res.iter_content(chunk_size):
    131. if chunk:
    132. f.write(chunk)
    133. progress_bar.update(len(chunk))
    134. except Exception as ee:
    135. progress_bar.close()
    136. return '下载异常,检查链接'
    137. progress_bar.close()
    138. filepath = self.upzip_file_new(zip_path, unzip_path)[0]
    139. print('filepath:', filepath)
    140. # 删除压缩包
    141. if os.path.exists(zip_path):
    142. os.remove(zip_path)
    143. # 老版本配置文件
    144. old_config = os.path.join(current_path, 'config', 'config.ini')
    145. # 新版本的配置文件路径
    146. new_config = os.path.join(filepath, '客户端配置文件.ini')
    147. print('new_config:', new_config)
    148. print('old_config:', old_config)
    149. if os.path.exists(new_config) and os.path.exists(old_config):
    150. os.remove(new_config)
    151. if os.path.exists(old_config):
    152. if not os.path.exists(os.path.join(filepath, 'config')):
    153. os.makedirs(os.path.join(filepath, 'config'))
    154. shutil.copy(old_config, new_config)
    155. self.auto_update_setup(filepath)
    156. def make_updater_bat(self, filepath):
    157. filepath = os.path.abspath(filepath)
    158. # 当前进程ID
    159. pid = os.getpid()
    160. current_path = os.path.dirname(os.path.realpath(sys.argv[0]))
    161. print('current_path:', current_path)
    162. # 删除临时文件夹路径
    163. new_file_dir = os.path.abspath(os.path.join(filepath, ".."))
    164. # old_file = filepath.replace(new_file_dir.replace('\\', '/'), current_path.replace('\\', '/')) # 旧的文件夹
    165. # old_file = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
    166. old_file = current_path
    167. old_file_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) # 上一级文件夹
    168. new_file = filepath # 新版本文件夹
    169. # 运行exe路径
    170. # new_file_exe = os.path.join(filepath, f'{self.app_name}.exe').replace('\\', '/')
    171. # new_file_exe = new_file_exe.replace(new_file_dir.replace('\\', '/'), old_file_dir.replace('\\', '/'))
    172. new_file_exe = os.path.join(current_path, f'{self.app_name}.exe').replace('\\', '/')
    173. print('filepath:', filepath)
    174. print('new_file_dir:', new_file_dir.replace('\\', '/'))
    175. print('current_path:', current_path.replace('\\', '/'))
    176. # 自动更新脚本
    177. """
    178. 关闭当前进程,删除所有文件除了updater.bat,将新版本下的所有文件移到当前文件夹下,访问新exe文件
    179. """
    180. bat_name = 'updater.bat'
    181. bat_path = os.path.join(old_file, bat_name)
    182. print('bat_path:', bat_path)
    183. with open(bat_path, 'w', encoding='gbk') as updater:
    184. updater.write(textwrap.dedent(f'''\
    185. @echo off
    186. echo 正在更新[{self.app_name}]最新版本,请勿关闭窗口...
    187. ping -n 2 127.0.0.1
    188. taskkill -f -pid {pid}
    189. echo 正在复制[{self.app_name}],请勿关闭窗口...
    190. for /D %%a in (*) do (
    191.    set str1="%%a"
    192. if "!str1!" == "!str1:{old_filename}=!" if "!str1!" == "!str1:{old_filename}=!" (
    193.    echo 删除不包含old_file的文件夹: %%a
    194. move /y "%%a" %{old_path}%
    195.    )
    196. )
    197. for %%i in (*) DO (
    198. if not %%i=={bat_name} if not %%i=={old_filename}(
    199. echo 删除文件:%%i
    200. :: del /s /q "%%i"
    201. move /y "%%i" %{old_path}%
    202. )
    203. )
    204. for %%a in (*) do (
    205.    set str1=%%a
    206. if "!str1!" == "!str1:{bat_name}=!" if "!str1!" == "!str1:{bat_name}=!" (
    207.    echo 删除不包含bat的文件: %%a
    208. move /y "%%a" %{old_path}%
    209.    )
    210. )
    211. ping -n 2 127.0.0.1
    212. xcopy /s /e /y /q "{new_file}\*" "{current_path}"
    213. ping -n 2 127.0.0.1
    214. if exist "{new_file_dir}" rd /s /q "{new_file_dir}"
    215. echo 更新完成,等待自动启动{self.app_name}...
    216. ping -n 2 127.0.0.1
    217. "{new_file_exe}"
    218. exit
    219. '''))
    220. updater.flush()
    221. return bat_path
    222. def auto_update_setup(self, filepath):
    223. # 自动更新
    224. bat_path = self.make_updater_bat(filepath)
    225. subprocess.Popen(bat_path, encoding="gbk", shell=True)
    226. def run(self):
    227. version_info = self.get_version_info()
    228. if version_info and version_info["code"] == 200:
    229. if version_info['is_force_update'] == 0:
    230. # 可选择更新
    231. self.check_version(version_info)
    232. else:
    233. # 强制更新
    234. self.down_zip(version_info)
    235. if __name__ == '__main__':
    236. start = Unit_Test_Version()
    237. start.run()
    1. # -*- coding: utf-8 -*-
    2. import time
    3. from datetime import datetime
    4. from fastapi.middleware.cors import CORSMiddleware
    5. import uvicorn
    6. from pydantic import BaseModel
    7. import asyncio
    8. from fastapi import FastAPI, File, UploadFile, Form
    9. from link_sql import Link_Sql
    10. from tools import log_count
    11. from starlette.responses import FileResponse
    12. """
    13. http://127.0.0.1:19315/search_version
    14. post请求
    15. 参数:soft_type 软件类型
    16. data={
    17. "soft_type":1
    18. }
    19. 返回结果:
    20. {
    21. 'version': '1.0.2',
    22. 'zip_url': 'https://127.0.0.1:19315/zipfiles/xxxx.zip',
    23. 'file_name': '软件测试',
    24. 'remark': '版本测试', # 更新内容
    25. 'is_force_update': 0 # 0.可选择是否更新 1. 强制更新
    26. }
    27. """
    28. class Comment_Task():
    29. def __init__(self):
    30. self.loggings = log_count()
    31. self.db = Link_Sql(self.loggings)
    32. def search_version(self, item):
    33. """
    34. 查询版本信息
    35. """
    36. soft_type = item.soft_type
    37. sql = 'select version,zip_url,file_name,remark,is_force_update from plugin_version where soft_type=%s ORDER BY create_time desc'
    38. result_data = self.db.one_search(sql % soft_type, to_json=True)
    39. if result_data:
    40. result_data["code"] = 200
    41. else:
    42. result_data["code"] = 500
    43. print('result_data:', result_data)
    44. return result_data
    45. app = FastAPI(docs_url=None, redoc_url=None, openapi_url=None)
    46. app.add_middleware(
    47. CORSMiddleware,
    48. allow_origins=["*"], # 表示允许任何源
    49. allow_credentials=True,
    50. allow_methods=["*"],
    51. allow_headers=["*"],
    52. )
    53. class Update_Status(BaseModel):
    54. soft_type: int
    55. method_name_dic = {
    56. # 查询版本信息
    57. "search_version": Comment_Task().search_version,
    58. }
    59. @app.post('/search_version')
    60. async def search_version(item: Update_Status):
    61. method_name = "search_version"
    62. try:
    63. loop = asyncio.get_event_loop()
    64. method_ = method_name_dic[method_name]
    65. data = await loop.run_in_executor(None, method_, item)
    66. return data
    67. except:
    68. return {"code": 500, "message": "查询失败"}
    69. @app.get("/zipfiles/{file_path}/{file_name}")
    70. async def download(file_path: str, file_name: str):
    71. # 下载压缩包 zipfiles为在当前文件 建立的文件夹放置压缩包
    72. filename = f"zipfiles/{file_path}/{file_name}"
    73. print('filename:', filename)
    74. return FileResponse(filename, filename=f"{file_name}") #展示的下载的文件名
    75. if __name__ == "__main__":
    76. uvicorn.run(app, host="0.0.0.0", port=19315)

  • 相关阅读:
    flask vue跨域问题
    常用命令总结
    VirtualBox解决VERR_SUPDRV_COMPONENT_NOT_FOUND错误
    UDP和TCP的区别
    elasticsearch面试题
    如何创建属于自己的百度百科?这几个创建方法赶紧收藏
    JS基础习题
    邮编区号查询易语言代码
    论文复现--lightweight-human-pose-estimation-3d-demo.pytorch(单视角多人3D实时动作捕捉DEMO)
    Java相关编程思想
  • 原文地址:https://blog.csdn.net/lwdfzr/article/details/134013624