• PySimpleGUI小试牛刀之Tomcat项目部署工具


    之前写过一篇 python 自动化部署项目,实现了Tomcat项目的初级自动化部署功能,但是它有一个不足,只支持单个项目部署,也就是说,项目被高度定制化了,所有的配置信息都被固化在了程序里,无法扩展。所以今天,我们给它来个小小的升级,让这个工具以界面图形化的方式运行,在这个界面上,支持对要部署的Tomcat项目进行管理。该工具主要解决以下问题(痛点):

    系统作了负载均衡处理,项目被分开存放在了多个不同的机器上,同时每个机器上也有多个项目,每次部署的时候,都需要工程师们手动在不同的目录之间来回切换,手动复制、粘贴、备份系统,手动删除缓存文件,手动启停web服务,步骤繁琐且容易出错。此时,如果能有一款工具,能够帮助我们自动化完成这些操作,解放工程师们的双手,应该算是大功一件了!

    so,先来张效果图,让我们直观感受下。

     下面正式开始!

    1. 安装pysimplegui

    pip install pysimplegui

     2. 编写核心代码

    1. import os
    2. import json
    3. import time
    4. import shutil
    5. import threading
    6. import webbrowser
    7. from gui_helper import GuiHelper
    8. from auto_release import AutoRelease
    9. from PIL import Image as pil_image
    10. import PySimpleGUI as sg
    11. class ProjectDeploy():
    12. # 配置文件
    13. project_file = 'project.json'
    14. left_layout_key = 'left_layout'
    15. right_layout_key = 'right_layout'
    16. default_right_layout_key = 'default_right_layout'
    17. background_image = 'background_image.png'
    18. # 窗口元素的背景色
    19. element_background_color = '#FCFCFC'
    20. def run(self):
    21. self.load_win()
    22. '''
    23. 加载窗口
    24. '''
    25. def load_win(self, old_window=None):
    26. # 获取屏幕宽高
    27. (width, height) = GuiHelper.get_window_size()
    28. win_height = int(height)
    29. left_win_width = 150
    30. right_win_width = width - left_win_width
    31. default_right_column_layout = [
    32. GuiHelper.text_input('点击左侧按钮,新建一个项目'),
    33. ]
    34. image = pil_image.open(self.background_image)
    35. src_image_size = image.size
    36. background_image_size = (min(src_image_size[0], int(right_win_width*0.7)), min(src_image_size[1], int(win_height*0.7)))
    37. default_right_column_layout.append([sg.Image(filename=self.background_image, size=background_image_size)])
    38. left_columns = [
    39. sg.Column(self.load_left(), key=self.left_layout_key, size=(left_win_width, win_height), background_color=self.element_background_color)
    40. ]
    41. right_columns = [
    42. sg.Column(default_right_column_layout, key=self.default_right_layout_key, size=(right_win_width, win_height), background_color=self.element_background_color),
    43. sg.Column(self.load_right(), key=self.right_layout_key, visible=False, size=(right_win_width, win_height), background_color=self.element_background_color, scrollable=True, vertical_scroll_only=True)
    44. ]
    45. left_frame = sg.Frame('', [left_columns], background_color=self.element_background_color, border_width=1)
    46. right_frame = sg.Frame('', [right_columns], background_color=self.element_background_color, border_width=0)
    47. layout = [[
    48. sg.Column([[left_frame]], pad=(0,0), background_color=self.element_background_color),
    49. sg.Column([[right_frame]], pad=(30,0), background_color=self.element_background_color)
    50. ]]
    51. if old_window is not None:
    52. old_window.close()
    53. window = GuiHelper.create_window('Tomcat项目部署工具', layout = layout)
    54. event_list = self.load_events()
    55. if event_list is not None:
    56. for event_item in event_list:
    57. window[event_item[0]].set_cursor(cursor='hand2')
    58. GuiHelper.listen_window(window, event_list)
    59. GuiHelper.close_window(window)
    60. '''
    61. 加载左侧布局
    62. '''
    63. def load_left(self):
    64. left_column_layout = [
    65. [GuiHelper.button('主页', None, True, 10)],
    66. [GuiHelper.button('新增项目', '#337AB7', True, 10)],
    67. ]
    68. project_list = self.get_project_list()
    69. project_names = list(project_list.keys())
    70. for index, name in enumerate(project_names):
    71. left_column_layout.append(GuiHelper.text_input('{}. {}'.format(index+1, name)))
    72. return left_column_layout
    73. '''
    74. 加载右侧布局
    75. '''
    76. def load_right(self):
    77. button_layout = [
    78. sg.Column([[]], size=(150, 30), background_color=self.element_background_color),
    79. sg.Column([[
    80. GuiHelper.button('保存'),
    81. GuiHelper.button('删除', '#FF5722', False),
    82. GuiHelper.button('开始部署', '#337AB7', False, 10),
    83. ]], key='btn_group', justification='left', pad=(0, 30), background_color=self.element_background_color)
    84. ]
    85. right_column_layout = [
    86. GuiHelper.text_input('请输入基本信息'),
    87. GuiHelper.text_input('项目标记', 'project_name'),
    88. GuiHelper.text_input('Tomcat服务名称', 'service_name'),
    89. GuiHelper.text_input('Tomcat服务端口', 'service_port'),
    90. GuiHelper.text_input('Tomcat工作目录', 'work_dir', None, None, 'folder'),
    91. GuiHelper.text_input('Tomcat缓存目录', 'cache_dir', None, None, 'folder'),
    92. GuiHelper.text_input('war包文件', 'war_file_name', None, None, 'files'),
    93. GuiHelper.text_input('备份目录', 'backup_dir', None, None, 'folder'),
    94. GuiHelper.text_input('应用访问URL', 'url'),
    95. button_layout,
    96. GuiHelper.text_output('process_info', 5, 60, False),
    97. ]
    98. return right_column_layout
    99. '''
    100. 切换右侧布局
    101. @show_right_layout True 显示主布局,False 显示默认布局
    102. '''
    103. def swap_right_layout(self, window, show_right_layout=True):
    104. show_default_right_layout_key = False
    105. if not show_right_layout:
    106. show_default_right_layout_key = True
    107. window[self.default_right_layout_key].update(visible=show_default_right_layout_key)
    108. window[self.right_layout_key].update(visible=show_right_layout)
    109. '''
    110. 事件列表
    111. '''
    112. def load_events(self):
    113. event_list = [
    114. ('主页', self.home),
    115. ('新增项目', self.add),
    116. ('删除', self.delete),
    117. ('保存', self.save),
    118. ('开始部署', self.deploy)
    119. ]
    120. project_list = self.get_project_list()
    121. project_names = list(project_list.keys())
    122. for index, name in enumerate(project_names):
    123. event_list.append(('{}. {}'.format(index+1, name), self.show_detail))
    124. return event_list
    125. '''
    126. 获取项目名称列表
    127. '''
    128. def get_project_list(self):
    129. result = self.read_txt(self.project_file)
    130. if result is None:
    131. result = "{}"
    132. project_list = json.loads(result)
    133. return project_list
    134. '''
    135. 返回主页
    136. '''
    137. def home(self, values, window, event):
    138. self.swap_right_layout(window, False)
    139. '''
    140. 新增项目
    141. '''
    142. def add(self, values, window, event):
    143. self.swap_right_layout(window)
    144. for key in values.keys():
    145. # 以_c结尾表示该控件为文件选择按钮
    146. if key.endswith('_c'):
    147. window[key].update("选择...")
    148. else:
    149. window[key].update("")
    150. window['保存'].update(visible=True)
    151. window['删除'].update(visible=False)
    152. window['开始部署'].update(visible=False)
    153. window['btn_group'].update(visible=True)
    154. window['process_info_col'].hide_row()
    155. '''
    156. 查看项目信息
    157. '''
    158. def show_detail(self, values, window, event):
    159. project_list = self.get_project_list()
    160. project_names = list(project_list.keys())
    161. for index, name in enumerate(project_names):
    162. if event == '{}. {}'.format(index+1, name):
    163. self.update_right_text(project_list[name], window)
    164. window['保存'].update(visible=False)
    165. window['process_info_col'].hide_row()
    166. window['删除'].update(visible=True)
    167. window['开始部署'].update(visible=True)
    168. window['btn_group'].update(visible=True)
    169. '''
    170. 更新元素内容
    171. '''
    172. def update_right_text(self, values, window):
    173. self.swap_right_layout(window)
    174. keys = list(values.keys())
    175. for key in keys:
    176. # 以_c结尾表示该控件为文件选择按钮
    177. if key.endswith('_c'):
    178. window[key].update("选择...")
    179. else:
    180. window[key].update(values[key])
    181. '''
    182. 保存项目信息
    183. '''
    184. def save(self, values, window, event):
    185. if not self.verify(values):
    186. return False
    187. if(not GuiHelper.confirm('确定保存吗')):
    188. return False
    189. result = self.read_txt(self.project_file)
    190. if result is None:
    191. result = "{}"
    192. result = json.loads(result)
    193. if values['project_name'] in result:
    194. GuiHelper.alert('该项目标记已存在')
    195. return False
    196. result[values['project_name']] = values
    197. project.write_txt('project.json', json.dumps(result, ensure_ascii=False))
    198. GuiHelper.alert('保存成功')
    199. self.load_win(window)
    200. return True
    201. '''
    202. 删除项目
    203. '''
    204. def delete(self, values, window, event):
    205. if(not GuiHelper.confirm('确定删除吗')):
    206. return False
    207. result = self.read_txt(self.project_file)
    208. if result is None:
    209. result = "{}"
    210. result = json.loads(result)
    211. if values['project_name'] in result:
    212. del result[values['project_name']]
    213. project.write_txt('project.json', json.dumps(result, ensure_ascii=False))
    214. GuiHelper.alert('删除成功')
    215. self.load_win(window)
    216. return True
    217. '''
    218. 开始部署
    219. '''
    220. def deploy(self, values, window, event):
    221. if not self.verify(values):
    222. return False
    223. if(not GuiHelper.confirm('确定开始部署吗')):
    224. return False
    225. window['删除'].update(visible=False)
    226. window['开始部署'].update(visible=False)
    227. window['btn_group'].update(visible=False)
    228. window['process_info'].update(visible=True)
    229. window['process_info_col'].update(visible=True)
    230. window['process_info_col'].unhide_row()
    231. threading.Thread(target=self._deploy, args=(values, window,), daemon=True).start()
    232. def _deploy(self, values, window):
    233. # 应用端口号
    234. port = values['service_port']
    235. # 服务名称
    236. service_name = values['service_name']
    237. # war包文件列表,多个文件用英文封号隔开
    238. war_file_names = values['war_file_name']
    239. # 项目所在目录
    240. work_dir = values['work_dir']
    241. # 备份根目录
    242. backup_dir = values['backup_dir']
    243. # 缓存目录
    244. cache_dir = values['cache_dir']
    245. # 检测url
    246. url = values['url']
    247. try:
    248. start_time = int(time.time())
    249. war_file_list = war_file_names.split(';');
    250. # 停止服务
    251. hour_time = time.strftime('%H:%M:%S', time.localtime(time.time()))
    252. GuiHelper.print_info(window, "{}【1/5】正在停止服务 {},请稍候...".format(hour_time, service_name))
    253. AutoRelease.stop_service(service_name)
    254. during_time_stop = 0;
    255. while True:
    256. pid = AutoRelease.get_pid(port)
    257. if pid == "" or pid == "0":
    258. hour_time = time.strftime('%H:%M:%S', time.localtime(time.time()))
    259. GuiHelper.print_info(window, "{}【1/5】服务 {} 已停止".format(hour_time, service_name))
    260. break
    261. time.sleep(1)
    262. during_time_stop += 1
    263. if during_time_stop == 60:
    264. hour_time = time.strftime('%H:%M:%S', time.localtime(time.time()))
    265. GuiHelper.print_info(window, "{}【1/5】服务 {} 停止超时,请手动停止".format(hour_time, service_name))
    266. window.refresh()
    267. break
    268. # 备份原文件
    269. for index, war_file in enumerate(war_file_list):
    270. if not os.path.isfile(war_file):
    271. hour_time = time.strftime('%H:%M:%S', time.localtime(time.time()))
    272. GuiHelper.print_info(window, "{}【2/5】当前目录未找到文件 {}".format(hour_time, war_file))
    273. continue
    274. # 分离路径和文件名
    275. (src_file_path, src_file_name) = os.path.split(war_file)
    276. # 分离文件名和后缀
    277. (base_file_name, file_suffix) = os.path.splitext(src_file_name)
    278. hour_time = time.strftime('%H:%M:%S', time.localtime(time.time()))
    279. GuiHelper.print_info(window, "{}【2/5】正在备份原文件 {},请稍候...".format(hour_time, base_file_name))
    280. zip_file_name = base_file_name+'.zip'
    281. date_str = time.strftime('%Y%m%d', time.localtime(time.time()))
    282. project_dir = work_dir+"/"+base_file_name
    283. out_zip_dir = "{}/{}".format(backup_dir, date_str)
    284. if not os.path.exists(out_zip_dir):
    285. os.makedirs(out_zip_dir)
    286. if AutoRelease.archive_file(out_zip_dir+"/"+zip_file_name, project_dir) :
    287. hour_time = time.strftime('%H:%M:%S', time.localtime(time.time()))
    288. GuiHelper.print_info(window, "{}【2/5】文件 {} 备份完成,路径:{}".format(hour_time, base_file_name, out_zip_dir+"/"+zip_file_name))
    289. # 部署新文件
    290. for index, war_file in enumerate(war_file_list):
    291. if not os.path.isfile(war_file):
    292. hour_time = time.strftime('%H:%M:%S', time.localtime(time.time()))
    293. GuiHelper.print_info(window, "{}【3/5】当前目录未找到文件 {}".format(hour_time, war_file))
    294. continue
    295. # 分离路径和文件名
    296. (src_file_path, src_file_name) = os.path.split(war_file)
    297. # 分离文件名和后缀
    298. (base_file_name, file_suffix) = os.path.splitext(src_file_name)
    299. hour_time = time.strftime('%H:%M:%S', time.localtime(time.time()))
    300. GuiHelper.print_info(window, "{}【3/5】部署新文件 {},请稍候...".format(hour_time, src_file_name))
    301. date_str = time.strftime('%Y%m%d', time.localtime(time.time()))
    302. project_dir = work_dir+"/"+base_file_name
    303. out_zip_dir = "{}/{}".format(backup_dir, date_str)
    304. AutoRelease.copy_file(war_file, out_zip_dir)
    305. if os.path.isfile(work_dir+"/"+src_file_name):
    306. os.remove(work_dir+"/"+src_file_name)
    307. if os.path.exists(project_dir):
    308. shutil.rmtree(project_dir, True)
    309. if os.path.exists(cache_dir):
    310. shutil.rmtree(cache_dir, True)
    311. AutoRelease.copy_file(war_file, work_dir)
    312. hour_time = time.strftime('%H:%M:%S', time.localtime(time.time()))
    313. GuiHelper.print_info(window, "{}【3/5】新文件 {} 部署完成".format(hour_time, src_file_name))
    314. # 启动服务
    315. hour_time = time.strftime('%H:%M:%S', time.localtime(time.time()))
    316. GuiHelper.print_info(window, "{}【4/5】正在启动服务 {},请稍候...".format(hour_time, service_name))
    317. AutoRelease.start_service(service_name)
    318. during_time_start = 0
    319. while True:
    320. if AutoRelease.get_pid(port) != "":
    321. hour_time = time.strftime('%H:%M:%S', time.localtime(time.time()))
    322. GuiHelper.print_info(window, "{}【4/5】服务 {} 已启动".format(hour_time, service_name))
    323. break
    324. time.sleep(1)
    325. during_time_start += 1
    326. if during_time_start == 60:
    327. hour_time = time.strftime('%H:%M:%S', time.localtime(time.time()))
    328. GuiHelper.print_info(window, "{}【4/5】服务 {} 启动超时,请手动启动".format(hour_time, service_name))
    329. break
    330. # 检查应用
    331. hour_time = time.strftime('%H:%M:%S', time.localtime(time.time()))
    332. GuiHelper.print_info(window, "{}【5/5】正在检测应用是否可以正常访问,请稍候...".format(hour_time))
    333. during_time_access = 0
    334. while True:
    335. if AutoRelease.get_http_status(url):
    336. hour_time = time.strftime('%H:%M:%S', time.localtime(time.time()))
    337. GuiHelper.print_info(window, "{}【5/5】应用已可以正常访问".format(hour_time))
    338. break
    339. time.sleep(5)
    340. during_time_access += 5
    341. if during_time_access == 60:
    342. hour_time = time.strftime('%H:%M:%S', time.localtime(time.time()))
    343. GuiHelper.print_info(window, "{}【5/5】应用访问超时".format(hour_time))
    344. break
    345. except Exception as e:
    346. hour_time = time.strftime('%H:%M:%S', time.localtime(time.time()))
    347. GuiHelper.print_info(window, "{}部署异常:{}".format(hour_time, str(e)))
    348. else:
    349. GuiHelper.print_info(window, "{0},用时 {1} 秒".format("部署完毕", int(time.time()) - start_time))
    350. webbrowser.open(url)
    351. window['删除'].update(visible=True)
    352. window['开始部署'].update(visible=True)
    353. window['btn_group'].update(visible=True)
    354. def verify(self, values):
    355. keys = {
    356. 'project_name': '项目标记名称不能为空',
    357. 'service_name': '服务名称不能为空',
    358. 'service_port': '端口号不能为空',
    359. 'work_dir': '请选择工作目录',
    360. 'cache_dir': '请选择缓存目录',
    361. 'war_file_name': '请选择war包文件',
    362. 'backup_dir': '请选择项目备份目录',
    363. 'url': '访问URL不能为空',
    364. }
    365. for key, text in keys.items():
    366. if GuiHelper.is_empty(values, key):
    367. GuiHelper.alert(text)
    368. return False
    369. return True
    370. def read_txt(self, file, encoding="utf-8"):
    371. if not os.path.isfile(file):
    372. return None
    373. with open(file, "r", encoding=encoding) as f:
    374. result = f.read()
    375. return result.rstrip()
    376. def write_txt(self, file, content, overwrite=True, encoding="utf-8"):
    377. model = "w+" if overwrite else "a+"
    378. with open(file, model, encoding=encoding) as f:
    379. f.write(content)
    380. if __name__ == "__main__":
    381. project = ProjectDeploy()
    382. project.run()

    3. 打包运行

     工具虽然以界面方式运行,但是部分操作需要与命令行窗口进行交互,所以打包的时候请不要添加 -w 参数,否则会报 “句柄无效” 错误,直接用如下命令打包即可。

    pyinstaller -F project_deploy.py

    最终的成品如下:

     

  • 相关阅读:
    nginx(2)
    【毕业设计】基于javaEE+SSH+MySql+MVC的动漫论坛设计与实现(毕业论文+程序源码)——动漫论坛
    Spring Boot文档阅读笔记-3 Ways to Add Custom Header in Spring SOAP Request
    Java并发(二十一)----wait notify介绍
    leetcode top100(10) 和为 K 的子数组
    ActiveMQ window安装、修改密码、启动一闪而过、设置8161端口仅本地访问
    进程通信(2) ----- 信号
    可选的优化器:Adam、SGD、Adagrad、RMSprop、Sparse Adam
    宝塔面板快速搭建贪吃蛇小游戏web网站 - 无需云服务器,网站发布上线
    电脑重装系统后鼠标动不了该怎么解决
  • 原文地址:https://blog.csdn.net/tdcqfyl/article/details/126099477