Files
Inventory-Management-System/tools/_exe_util.py
T
star 7d34a481b3 feat: 库存管理系统 MaterialWMS 初始版本
- Flask + SQLAlchemy 物料/入库/出库/BOM/生产订单全模块管理
- PyInstaller 打包为单文件 EXE (双击运行, 自动建库)
- 物料维度: 入库总数量 / 消耗总数量 / 逐次入库流水(含时间)
- BOM 详情展示组成物料的规格型号与单套用量
- 支持 Altium / 嘉立创 BOM 批量导入(文本与 Excel 双通道)
- 内置端到端验证脚本(tools/_exe_util.py 等, 清理进程树防残留)
2026-09-11 15:41:56 +08:00

60 lines
2.1 KiB
Python

# -*- coding: utf-8 -*-
"""EXE 启动 / 清理公共工具。
背景(踩坑记录):
PyInstaller `--onefile --windowed` 的启动器(runw.exe)会**派生子进程**运行真正的
Flask 服务器。若只 `p.terminate()` 父进程,子进程会残留并继续霸占 8080;
Windows 允许同一端口被多个 socket 绑定,于是后续请求会被**随机路由到旧实例**,
表现为: 随机 500、改了模板/代码"不生效"、时好时坏的返回内容。
正确做法:
1) 启动前按可执行文件路径清掉 dist 下所有 EXE(兼容 *.old.exe / _prev_*.exe 等改名备份);
2) 收尾用 `taskkill /F /T /PID` 杀掉**整棵进程树**,再兜底按路径清一次。
"""
import os
import subprocess
import time
EXE = r"D:\2026-09-10-task-20\material_wms\dist\MaterialWMS.exe"
def kill_dist_exes(exe_path=EXE):
"""杀掉 exe_path 所在目录下的所有 EXE 进程(按路径匹配任意文件名)。"""
dist = os.path.dirname(exe_path)
ps = (
"$d = '%s'; "
"Get-CimInstance Win32_Process | "
"Where-Object { $_.ExecutablePath -like ($d + '\\*.exe') } | "
"ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }"
) % dist
try:
subprocess.run(["powershell", "-NoProfile", "-Command", ps],
capture_output=True, timeout=30)
except Exception as e:
print(" [warn] kill_dist_exes:", e)
def start_exe(exe_path=EXE):
"""清残留 -> 启动 EXE -> 返回 Popen(用完务必 stop_exe)。"""
kill_dist_exes(exe_path)
time.sleep(1)
return subprocess.Popen([exe_path])
def stop_exe(p):
"""杀掉整棵进程树并兜底清理(幂等, 传入 None 安全)。"""
if p is not None:
try:
subprocess.run(["taskkill", "/F", "/T", "/PID", str(p.pid)],
capture_output=True, timeout=15)
except Exception:
pass
try:
p.wait(timeout=5)
except Exception:
try:
p.kill()
except Exception:
pass
kill_dist_exes()