您现在的位置是:首页 >技术杂谈 >手摸手系列之用Python制作Windows小工具及将Python封装为exe可执行程序网站首页技术杂谈

手摸手系列之用Python制作Windows小工具及将Python封装为exe可执行程序

dearmrzhang 2024-09-06 00:01:03
简介手摸手系列之用Python制作Windows小工具及将Python封装为exe可执行程序

最近在做集团的SaaS平台项目,有一个小小的特殊的需求,就是需要不懂技术的业务小姐姐们根据一个申请批次号删除整个流程的数据,且此功能不可集成到平台中。考虑到小姐姐们不懂代码和SQL语法,以及数据的安全性问题。故用Python封装了一个exe小工具,方便她们操作。

为什么要将 Python 程序打包为 exe 可执行文件?
众所周知,Python 程序的运行必须要有 Python 的环境,但是程序编出来是用的,如果是给别人用,而他/她的电脑上又没有 Python 程序运行的环境怎么办呢?总不能让他/她去安装一个吧?这时我们就要将 Python 程序打包为 exe 文件。这样,在 Windows 平台下,就可以直接运行该程序,不论有没有 Python 环境。

代码比较简单,使用内置的Tkinter库构建图形用户界面(GUI),用户输入参数,然后点击按钮执行SQL来删除付款申请的流程的相关数据。下面我们来一步步实现。

一、主要代码实现

  1. 导入需要的库
import tkinter as tk
from tkinter import messagebox
from tkinter import ttk
import pymysql
  1. 创建一个窗口对象并设置标题
window = tk.Tk()
window.title("删除付款申请-小姐姐定制版")
  1. 设置窗口的尺寸和位置
# 获取屏幕宽度和高度
screen_width = window.winfo_screenwidth()
screen_height = window.winfo_screenheight()

# 设置窗口尺寸和位置
window_width = 400
window_height = 200
x = (screen_width - window_width) // 2
y = (screen_height - window_height) // 2
window.geometry(f"{window_width}x{window_height}+{x}+{y}")
  1. 创建一个下拉列表框(Combobox)并设置选项
# selected_option = tk.StringVar()
# selected_option.set(options[1])
# dropdown = ttk.OptionMenu(window, selected_option, *options)
# dropdown.pack()
options = ["测试服", "正式服"]
combobox = ttk.Combobox(window, values=options, width=10)
combobox.current(1)  # 设置默认选中的选项
combobox.pack(pady=10)
  1. 创建一个标签和一个文本框用于输入付款申请批次号
label = tk.Label(window, text="请输入付款申请批次号:")
label.pack()  # 添加标签并设置间距

entry = tk.Entry(window)
entry.pack(pady=20)  # 垂直居中
  1. 创建一个提交按钮,点击时调用submit函数
button = tk.Button(window, text="提交", command=submit)
button.pack()  
  1. 定义一个名为submit的函数,该函数在点击"提交"按钮时被调用,其中用户输入的值作为SQL参数,根据用户选择下拉菜单项去判断执行正式服还是测试服。
  def submit():
    input_text = entry.get()
    if len(input_text) == 0:
        messagebox.showinfo("提示", "请输入付款申请批次号!")
        return

    selected_value = combobox.get()
    print("选择的值是:", selected_value)
    host = "80.xxx.xxx.xxx"
    password = "testpassword"
    if selected_value == "正式服":
        messagebox.showinfo("提示", "当前正式服数据库,请谨慎操作!!!")
        messagebox.showinfo("免责声明", "产生的一切后果,由您自行承担!!!")
        host = "10.xxx.xxx.xxx"
        password = "prodpassword"
    else:
        host = "80.xxx.xxx.xxx"
        password = "testpassword"

    # 创建数据库连接
    conn = pymysql.connect(
        host=host, user="root", password=password, database="yorma-platform"
    )

    # 打开数据库可能会有风险,所以添加异常捕捉
    try:
        with conn.cursor() as cursor:
            # 定义多条DELETE语句
            delete_statements = [
                f"""DELETE wf_hist_task_actor,
                wf_task_actor,
                wf_hist_task,
                wf_task,
                wf_hist_order,
                wf_order,
                WF_REL_BUSINESS
                FROM
                    wf_hist_task_actor
                    JOIN wf_hist_task ON wf_hist_task.id = wf_hist_task_actor.task_Id
                    JOIN wf_hist_order ON wf_hist_order.id = wf_hist_task.order_Id
                    JOIN wf_order ON wf_order.id = wf_hist_order.id
                    JOIN wf_task ON wf_task.order_Id = wf_order.id
                    JOIN wf_task_actor ON wf_task.id = wf_task_actor.task_Id
                    JOIN WF_REL_BUSINESS ON WF_REL_BUSINESS.ORDER_ID = wf_order.id
                    JOIN ACCOUNT_PUT_PAYMENT ON ACCOUNT_PUT_PAYMENT.ID = WF_REL_BUSINESS.BUSINESS_ID 
                WHERE
                    ACCOUNT_PUT_PAYMENT.PUT_NO = '{input_text}'""",
                f"""DELETE WF_REL_BUSINESS 
                FROM
                    WF_REL_BUSINESS
                    JOIN ACCOUNT_PUT_PAYMENT ON ACCOUNT_PUT_PAYMENT.ID = WF_REL_BUSINESS.BUSINESS_ID 
                WHERE
                    ACCOUNT_PUT_PAYMENT.PUT_NO = '{input_text}'""",
            ]
            # 执行多条DELETE语句
            total_rows_affected = 0
            for statement in delete_statements:
                cursor.execute(statement)
                rows_affected = cursor.rowcount
                total_rows_affected += rows_affected
            # 提交更改
            conn.commit()
    except Exception as e:
        # 发生错误时回滚
        conn.rollback()
        print("数据库操作异常:
", e)
        messagebox.showinfo("提示", "数据库操作异常!")
    finally:
        # 不管成功还是失败,都要关闭数据库连接
        conn.close()
    messagebox.showinfo("提示", f"已删除 {total_rows_affected} 条数据!")
    print("您输入的文本是:", input_text)
  1. 运行窗口主循环
  window.mainloop()

最终代码(敏感信息已过滤):

"""
Author: ZHANGCHAO
Date: 2023-06-06 09:27:34
LastEditors: ZHANGCHAO
LastEditTime: 2023-06-06 10:10:22
FilePath: pythonProject删除付款申请专用.py
"""
import tkinter as tk
from tkinter import messagebox
from tkinter import ttk
import pymysql


def submit():
    input_text = entry.get()
    if len(input_text) == 0:
        messagebox.showinfo("提示", "请输入付款申请批次号!")
        return

    selected_value = combobox.get()
    print("选择的值是:", selected_value)
    host = "80.xxx.xxx.xxx"
    password = "testpassword"
    if selected_value == "正式服":
        messagebox.showinfo("提示", "当前正式服数据库,请谨慎操作!!!")
        messagebox.showinfo("免责声明", "产生的一切后果,由您自行承担!!!")
        host = "10.xxx.xxx.xxx"
        password = "prodpassword"
    else:
        host = "80.xxx.xxx.xxx"
        password = "testpassword"

    # 创建数据库连接
    conn = pymysql.connect(
        host=host, user="root", password=password, database="yorma-platform"
    )

    # 打开数据库可能会有风险,所以添加异常捕捉
    try:
        with conn.cursor() as cursor:
            # 定义多条DELETE语句
            delete_statements = [
                f"""DELETE wf_hist_task_actor,
                wf_task_actor,
                wf_hist_task,
                wf_task,
                wf_hist_order,
                wf_order,
                WF_REL_BUSINESS
                FROM
                    wf_hist_task_actor
                    JOIN wf_hist_task ON wf_hist_task.id = wf_hist_task_actor.task_Id
                    JOIN wf_hist_order ON wf_hist_order.id = wf_hist_task.order_Id
                    JOIN wf_order ON wf_order.id = wf_hist_order.id
                    JOIN wf_task ON wf_task.order_Id = wf_order.id
                    JOIN wf_task_actor ON wf_task.id = wf_task_actor.task_Id
                    JOIN WF_REL_BUSINESS ON WF_REL_BUSINESS.ORDER_ID = wf_order.id
                    JOIN ACCOUNT_PUT_PAYMENT ON ACCOUNT_PUT_PAYMENT.ID = WF_REL_BUSINESS.BUSINESS_ID 
                WHERE
                    ACCOUNT_PUT_PAYMENT.PUT_NO = '{input_text}'""",
                f"""DELETE WF_REL_BUSINESS 
                FROM
                    WF_REL_BUSINESS
                    JOIN ACCOUNT_PUT_PAYMENT ON ACCOUNT_PUT_PAYMENT.ID = WF_REL_BUSINESS.BUSINESS_ID 
                WHERE
                    ACCOUNT_PUT_PAYMENT.PUT_NO = '{input_text}'""",
            ]
            # 执行多条DELETE语句
            total_rows_affected = 0
            for statement in delete_statements:
                cursor.execute(statement)
                rows_affected = cursor.rowcount
                total_rows_affected += rows_affected
            # 提交更改
            conn.commit()
    except Exception as e:
        # 发生错误时回滚
        conn.rollback()
        print("数据库操作异常:
", e)
        messagebox.showinfo("提示", "数据库操作异常!")
    finally:
        # 不管成功还是失败,都要关闭数据库连接
        conn.close()
    messagebox.showinfo("提示", f"已删除 {total_rows_affected} 条数据!")
    print("您输入的文本是:", input_text)


window = tk.Tk()
window.title("删除付款申请-小姐姐定制版")

# 获取屏幕宽度和高度
screen_width = window.winfo_screenwidth()
screen_height = window.winfo_screenheight()

# 设置窗口尺寸和位置
window_width = 400
window_height = 200
x = (screen_width - window_width) // 2
y = (screen_height - window_height) // 2
window.geometry(f"{window_width}x{window_height}+{x}+{y}")

options = ["测试服", "正式服"]
# selected_option = tk.StringVar()
# selected_option.set(options[1])
# dropdown = ttk.OptionMenu(window, selected_option, *options)
# dropdown.pack()
combobox = ttk.Combobox(window, values=options, width=10)
combobox.current(1)  # 设置默认选中的选项
combobox.pack(pady=10)

label = tk.Label(window, text="请输入付款申请批次号:")
label.pack()  # 添加标签并设置间距

entry = tk.Entry(window)
entry.pack(pady=20)  # 垂直居中

button = tk.Button(window, text="提交", command=submit)
button.pack()

window.mainloop()

上面代码创建了一个窗口,其中包含一个下拉列表框、一个文本输入框和一个提交按钮。当用户在文本框中输入付款申请批次号并点击提交按钮时,程序将根据选择的服(测试服或正式服)删除相关的数据,并在消息框中显示删除的数据行数。

二、打包为exe可执行程序

打包分类:

  • 一般的打包
    步骤最少,操作最简单,但是打包时间久,效果不理想(打包后文件太大,一般 100MB 以上)
  • 虚拟环境下的打包
    步骤稍多,操作略微复杂,但是打包快,效果好(打包后文件不大,一般 10MB 以内)
  • 多 Python 文件的打包
    步骤更多,操作更复杂,但是可以将多个 Python 文件都打包进去
  • 包含资源文件的打包
    步骤极为繁琐,操作非常复杂,但是可以把所有的文件都包含进去

要将Python代码封装为可执行程序(.exe),需要使用一些第三方库和工具。在这里我们使用PyInstaller库来创建独立的可执行文件

  1. 确保已经安装了Python和pip。

  2. 打开命令行终端,并使用以下命令安装PyInstaller:

    pip install pyinstaller  
    
  3. 打包 Python 文件
    输入如下的格式命令来打包

    Pyinstaller -option1 -option2 -... 删除付款申请小工具.py
    

    参数选项如下,只列举几个常用的:

参数选项解释说明
-F, -onefile只生成一个单个文件(只有一个 exe 文件)
-w, -windowed, -noconsole使用 Windows 子系统执行,当程序启动的时候不会打开命令行(只对 Windows 有效)
-D, -onedir打包多个文件,在dist中生成很多依赖文件,适合以框架形式编写工具代码,这样代码易于维护
-c, -nowindowed, -console使用控制台子系统执行(默认)(只对 Windows 有效)

现在我们用最常用的方式执行打包:

# -w 的意思就是exe运行的时候不弹出那个命令行(黑窗口)
Pyinstaller -F -w 删除付款申请小工具.py

当出现如下的文字(completed successfully.)时就代表打包成功了!然后目录里多了三个文件build 文件夹、dist 文件夹和 spec 文件,我们想要的 exe 文件就在新生成的 dist 文件夹里面,spec 文件和 build 文件夹就没用了,可以删除了。

image.png
image.png

运行生成的exe文件,界面如下:

image.png
输入测试,正常执行:
image.png

总结

Python通过这种方式打包成exe可执行程序还是很简单的。但是有个问题,这里用的打包方式产生的 exe 文件都比较大,这是因为 Pyinstaller 打包的时候会把你环境中的库和模块全部打包进去,这就会使一些你根本用不着的库和模块也被打包进去了!而且这些库被打包之后不仅会使 exe 文件变大,还会使其运行变卡变慢、变得十分臃肿。因此,一般情况下不建议这样的打包方式。可以用第二种方式进行打包 —— 虚拟环境下的打包。下期我们再讲!

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。