作者信息
作者: 小海波 项目地址: GitHub
🖼️1. 背景与痛点
你是否也经常遇到这种场景?
顾客在店里自助修图,店员得一直盯着计时,算时长、算张数、按计算器,最后再拿着二维码收钱。一忙起来就容易漏单,人一多就手忙脚乱。
最近接了一个线下门店的小项目,就是解决这个痛点。客户用的是像素蛋糕修图,想让整个计时收费流程全自动。
核心需求: "打开软件自动计时,导出前弹窗收费,付完钱才能导出。"
而且客户有多台不同配置的电脑(不同分辨率、不同 DPI 缩放、甚至不同 Windows 版本),要求每台都能稳定工作 —— 这成了后面最大的挑战。
🎯2. 项目简介( 项目是干嘛的)
这是一个运行在 Windows 上的桌面计时计费工具。针对安装了「像素蛋糕」的自助修图设备,程序逻辑如下:
- 检测到像素蛋糕启动 → 自动开始计时
- 顾客修完图点击"导出" → 立刻弹出一个全屏置顶的收费框
- 收费框显示:用了多久、导出了多少张、单价多少、总共多少钱、收款码
- 顾客扫码付款,店员输入密码确认收款
- 收费框关闭,导出流程恢复
计费公式:
总费用 = 使用时长(分钟)× 计时单价 + 导出张数 × 单张导出单价 (两个单价都可以在管理员后台随时改)
✅3. 已实现功能( 目前已经跑通的功能)
基础能力
✅ 后台监控像素蛋糕进程,自动开始/暂停计时 ✅ 自动计算总费用(时长 + 张数双维度计费) ✅ 弹出全屏置顶收费框,显示时长、张数、单价、总价、收款码 ✅ 管理员密码确认收款 ✅ 收款后恢复导出流程 ✅ 配置持久化保存(单价、密码、收款码路径等) ✅ 系统托盘图标,后台静默运行 ✅ 管理员后台面板,可实时调整计费参数 ✅ 降级处理,当OCR识别失败时,手动输入张数 ✅生命周期设计,像素蛋糕关闭后清空缓存,确保数据不会流入下一周期
导出检测与拦截(核心亮点 🔥)
✅ 6 条并行检测链路:窗口标题 / 导出子进程 / 视觉按钮识别 / OCR 文字识别 / 鼠标点击事件 / 居中对话框扫描 — 任意一条命中即触发弹窗
✅ OCR 读取导出张数("导出 X 张图片"),支持兜底默认值
✅ 进程挂起防抢跑:检测到导出瞬间挂起目标进程主线程,防止顾客趁空档跳过付费
✅ 异常安全保证:程序崩溃不会锁死目标软件,try-finally 全链路保护
✅ 多机兼容优化:不强制依赖单一检测手段,OCR 失败也不影响弹窗触发
💡 为什么需要 6 条检测链路? 因为线下门店的电脑配置千差万别。有的机器分辨率不一样导致坐标偏移,有的 DPI 缩放让窗口大小变化,有的 Windows 版本低没有最新 OCR 引擎。单条路不通不能让整个系统罢工,所以设计了"多路证据并行,任一命中即触发"的策略。
🔧4. 技术栈
| 技术 | 用途 |
|---|---|
| Python 3.8+ | 主语言 |
| PyQt5 | 全屏收费弹窗 + 托盘图标 + 管理后台 |
| psutil | 进程监控与生命周期管理 |
| pywin32 | Windows API(窗口查找、线程挂起/恢复、鼠标钩子) |
| Pillow | 收款码图片加载 + 截图处理 |
| Windows 内置 OCR | 识别导出张数(无需安装额外 OCR 包) |
| PyInstaller | 打包成单个独立 EXE,目标机器无需装 Python |
当前项目结构:
SoftwareUsageMeter/
├── main.py # 入口,托盘图标 + 后台线程启动
├── process_monitor.py # 进程监控 + 6路导出检测 + OCR(核心文件,2000+行)
├── timer_manager.py # 计时逻辑管理
├── payment_overlay.py # 收费弹窗 UI(PyQt5 全屏置顶)
├── admin_panel.py # 管理员后台(调参数、改密码、设收款码)
├── tray_icon.py # 系统托盘图标 + 右键菜单
├── config_manager.py # 配置读写(JSON 持久化)
├── build.bat / build.spec # 一键打包脚本
├── requirements.txt # Python 依赖
├── config.json # 运行时配置(单价、密码等)
└── resources/ # 图标等资源文件
🖥️5.页面展示
收费弹窗 (PaymentOverlay)
状态页面 (StatusWidget)
管理员验证 (PasswordDialog)
管理设置 (AdminPanel)
🧨6. 开发难点与踩坑记录
6.1 第三方软件没有开放接口,只能做"行为检测"
像素蛋糕没有开放导出 API,程序不能直接知道用户什么时候点击了导出,只能通过多种证据去判断:
是否出现导出窗口 是否出现导出相关子进程 屏幕上是否出现导出页 OCR 是否识别到"导出 X 张图片" 鼠标是否点击了右上角黄色导出按钮 是否进入"导出至本地"对话框
一开始我把 OCR 当成主要判断依据,结果问题很多。不同电脑的分辨率、缩放比例、字体渲染、语言包都会影响 OCR。后来改成了"多证据并行":OCR 只负责识别张数,不再决定是否弹收费框。只要导出窗口、导出子进程、黄色导出按钮、鼠标点击等任意强信号命中,就先触发收费流程。
6.2 导出张数识别踩了很多坑
导出张数最开始识别不准:导出 2 张识别成 1 张、导出 3 张识别成 77 张、导入和修改照片时也误触发收费、普通导出和"导出至本地"的界面结构还不一样。
最后发现最稳定的方式不是识别整个页面,而是裁剪导出框左上角摘要区域,例如"导出 2 张图片",比全屏 OCR 稳定很多。OCR 失败不能影响收费框弹出,只能影响张数显示,失败时切换为手动输入模式。
6.3 一个 OCR 问题,让我折腾了整整两天 🔥
这是整个项目最折磨人的一个 bug,也是最能体现"远程交付不可控"的例子。
现象: 我自己 Win11 和室友 Win10 上 OCR 完美识别张数,但雇主 Win10 上完全不识别。雇主电脑也装了中文语言包。
第一轮排查 —— 以为是语言包问题: 程序用的是 Windows 内置 OCR(Windows.Media.Ocr),于是让雇主跑 PowerShell 检测脚本。结果显示 OCR引擎创建失败。我心想:这不就是没装 OCR 语言包嘛,装了就行。结果雇主发现"设置 → 语言 → 中文 → 选项"里根本没有"基本输入/OCR"这个可选功能。查了才知道,Windows 10 某些版本中 OCR 是藏在"设置 → 应用 → 可选功能"里的,跟语言包不在一个地方。雇主用 Add-WindowsCapability 装好后,PowerShell 测试输出 zh-Hans-CN,OCR 引擎正常了。
第二轮排查 —— 超时问题: 满以为搞定了,结果雇主发来日志,全是 Windows OCR 超时(0.9s)。原来代码里居中对话框扫描的 OCR 超时设了 0.9 秒。Win11 上 PowerShell 冷启动 0.5 秒就够了,但 Win10 上 PowerShell 冷启动要 2-3 秒,0.9 秒根本来不及。于是我把超时从 0.9s 提到 3.0s,又在监控线程启动时做了 PowerShell 预热(事先跑一个 exit 0 让系统缓存 PowerShell 引擎),同时给所有失败的路径加了详细的诊断日志(returncode、stdout、stderr、耗时)。
第三轮排查 —— 窗口太小被拒: 超时修好后又发一版。这次日志清楚多了,OCR 不再超时,但全部返回空文本。关键日志出现在这里: GetWindowRect: hwnd=2754856, bounds=(720,341,1200,697), size=480x356 窗口太小,无法截图: hwnd=2754856, size=480x356 导出页截图快照失败
像素蛋糕导出对话框在雇主电脑上是 480×356 像素,而 capture_window_image() 里硬编码了最小尺寸门槛 500×350。宽度差了 20 像素,截图直接被拒,OCR 根本没机会看到对话框里的文字。把门槛降到 400×300 后,问题解决。
教训: 这个 bug 的根因其实就一行数字(500 改成 400),但找到它花了整整两天。过程中暴露的问题比根因本身更值得反思:
- 日志是第一生产力。 最初 OCR 失败没有任何日志输出原因,只能靠猜。后面加了 returncode、stdout、stderr、耗时的详细日志,3 轮迭代就定位到了 20 像素的问题。
- 自己的电脑不是交付环境。 Win11 上跑得好好的,Win10 上可能 PowerShell 慢 3 倍、导出对话框尺寸不一样。
- 阈值写死是定时炸弹。 500×350 这个数字是我凭感觉定的,完全没有考虑不同机器上窗口可能略小一点。如果在早期就用配置化的阈值或者至少打一行 info 日志,第一轮交付就能发现问题。
6.4 远程交付环境不可控
第二次交付时,雇主电脑上出现了"收费框不弹""程序卡死""app.log 没有新记录"等问题。排查发现雇主电脑缺少 Visual C++ 运行库。最后打包时把 msvcp140.dll、vcruntime140.dll、concrt140.dll 等一起带上。
这次之后我总结了交付 checklist:目标电脑是干净环境、缺运行库、安全软件拦截、路径混乱、运行了旧副本,每一项都可能致命。
6.5 用户操作也必须被设计进去
现场人员可能会乱点、重复启动程序、在程序没准备好时就点导出,甚至分不清等待遮罩和程序卡死。后来加了两个保护:启动说明提示框(必须先点"知道了"才能进入监控),以及导出前全屏等待遮罩(阻止重复点击,给收费框争取检测时间)。
6.6 锁定和解锁比想象中危险
收费弹窗出现后需要阻止用户在未付款前继续导出。但程序异常退出后可能残留锁定状态,导致像素蛋糕窗口不可操作。后来做了很多保护:退出前兜底恢复、启动时自恢复残留锁定、付款后同一次导出的尾迹不触发二次收费。
6.7 这次项目最大的收获
小单也会遇到真实工程问题。不是写完功能就结束了,还要考虑目标电脑缺运行库、第三方软件版本变化、OCR 不稳定、Windows 缩放比例不同、用户乱点、远程交付信息不完整、程序异常退出后的恢复、日志是否足够定位问题、exe 是否能在干净电脑上直接运行。
真正难的不是写出功能,而是让它在别人电脑上、别人操作下、各种奇怪环境里尽量稳定地工作。
📦7. 部署方式
打包后只需 3 个文件:
SoftwareUsageMeter.exe # 主程序(内嵌所有依赖,~15MB)
config.json # 计费配置
resources/icon.png # 托盘图标
目标机器不需要安装 Python、不需要安装任何依赖、不需要配置 OCR 环境,双击 exe 即可运行。Windows 10/11 自带的 OCR 引擎足够用。
🚀8. 后续计划
- 支持更多修图软件(不限于像素蛋糕),做成通用的"自助设备计时收费"方案
- 增加远程管理后台(Web 端查看各台设备的营收数据、在线改配置)
- 增加使用统计报表(每日/每周营收、高峰时段分析)
- 接入微信/支付宝支付回调实现自动确认收款(目前需店员手动输密码确认)
- 开发通用版配置向导,让非技术人员也能自行适配其他软件
💭9. 作者感悟
身份为学生/接单开发者,这个项目耗时一周多,修了不下 20 个 bug 才交付。
核心体会:桌面自动化不同于 Web 开发,面对的是黑盒窗口。需求简单(一句话能说清),但稳定交付背后全是细节。 Web 项目出错可刷新重启,桌面工具在门店出错就是客诉。拼的是在各种边缘情况下都不崩的能力。
这次多机兼容的重构让我深刻理解了一个道理:在自己电脑上跑通 ≠ 能交付。真正的考验是在别人的机器上能不能活下来。
欢迎交流方向: Windows 桌面自动化、OCR 应用、Hook 拦截、门店自助设备、PyQt 开发经验、PyInstaller 打包避坑。