modify: 台账更改插件

This commit is contained in:
2026-09-22 16:08:12 +08:00
parent f468845f67
commit e1c02a9b80
37 changed files with 6377 additions and 380 deletions
+217
View File
@@ -0,0 +1,217 @@
# 打包分发
把 GUI 打成**免安装 exe**,发给厂商内部同事(商务/售后)。两种方式各一个脚本,
都自带"私钥守卫 + 产物自检"。
```bash
uv sync --group dev # 装打包工具(pyinstaller / nuitka)
uv run python packaging/build_pyinstaller.py # 方式一:PyInstaller(快,默认单文件)
uv run python packaging/build_nuitka.py # 方式二:Nuitka(真编译,慢但更小更快)
# 产物
dist/pyinstaller/chongming-license-gui.exe
dist/nuitka/chongming-license-gui.exe
```
## 该选哪个
下表为**本机实测值**(Windows 11 / Python 3.14 / Qt 6.11.2 / MSVC 14.5,单文件模式):
| | PyInstaller | Nuitka |
| --- | --- | --- |
| 构建耗时 | **约 2.5 分钟**(实测 149s) | **约 10 分钟**(首次实测 1140s,含一次编译器重试) |
| 构建前提 | 无 | **需要 C 编译器**(MSVC 或 Nuitka 支持的 mingw64) |
| 单文件体积 | 54.8 MB | **38.6 MB** |
| 启动 + 跑完自检 | 2.7s | **1.8s** |
| 反编译难度 | 打包的 .pyc 可被还原 | **编译成 C,难得多** |
| 稳定性 | 生态成熟、踩坑资料多 | 需要针对 Python/Qt 版本调参(见下) |
**建议**:日常发给同事用 **Nuitka 单文件**(更小、启动更快、更难反编译);
需要快速出包/临时验证用 **PyInstaller**(两分半就好,且不需要编译器)。
> ⚠️ **要 PostgreSQL 支持就用 PyInstaller。** Nuitka 在 Python 3.13+ 上只能用 MSVC
> (`--mingw64` 被 Nuitka 明确拒绝),而 MSVC 编译 psycopg 的生成代码会
> `fatal error C1002`(编译器堆空间不足)—— 这是编译器上限,不是代码问题。
> Nuitka 脚本检测到这一点会**自动降级**为"不带 PG"的构建并告诉你;
> 也可以显式 `--no-postgres`。不带 PG 的包里,界面会如实提示
> 「未安装 PostgreSQL 驱动(可选功能)」,本地 SQLite 台账与签发校验全部照常。
## 常用参数
两个脚本参数一致:
| 参数 | 作用 |
| --- | --- |
| `--onedir` / `--standalone` | 打成目录而不是单文件。**启动更快**,适合放在固定办公机上;分发时整个目录一起拷 |
| `--console` | 保留控制台窗口。默认是**无控制台**(纯 GUI);排查"双击没反应"时用得上 |
| `--clean` | 构建前清理旧的 `dist/`(Nuitka 还会顺手删中间产物) |
| `--lto=no`(仅 Nuitka) | 关掉链接期优化 —— **显著加快构建**(默认开着,链接阶段实测要 6 分钟以上) |
| `--no-low-memory`(仅 Nuitka) | 关掉低内存模式。默认**开启**,见下方 C1002 说明 |
| `--no-selftest` | 跳过构建后的产物自检(不建议) |
| `--allow-sensitive-files` | 源码树里有私钥/`.lic`/台账库时仍继续构建(默认**中止**,见下) |
| `--msvc` / `--mingw64`(仅 Nuitka) | 指定编译器;默认自动挑选 |
## 两道安全闸门(重要)
**第一道:构建前扫源码树。** 命中 `license_private*.pem`、`*_private.pem`、
`*_private.key`、`*.lic`、`licenses.db` 一律**中止构建**并列出文件:
```
构建中止:源码树里存在私钥/授权文件/台账库,它们绝不能进入分发包。
- license_L20260921-A36014.lic
```
原因很直接:这个工具**含签发能力**。私钥一旦随包发出去,拿到的人就能自己签发有效授权;
`.lic` 与台账库则含客户名与机器指纹。确实只是临时放置时,可加
`--allow-sensitive-files` 跳过(会打印警告并列出文件)。
**第二道:构建后再查产物目录**,避免 onedir 模式把工作目录里的散落文件一起拷进去。
## 构建后自检(为什么必须有)
冻结失败往往是**静默**的 —— 界面能开,但图标全空白、复选框点不动,或者签发时才抛异常。
所以构建脚本会自动调用 ``<exe> --selftest`` 跑一遍真依赖,而且跑**两轮**:
| 轮次 | `QT_QPA_PLATFORM` | 验的是什么 |
| --- | --- | --- |
| 1 | `offscreen` | 无头环境也能验;CI 里可跑 |
| 2 | 系统原生(Windows 上是 `windows`) | **Qt 的 platforms 插件真的打进产物了** —— 少了它,开发机上一切正常,同事双击闪退 |
自检覆盖的正是打包最容易漏的东西:
- **QtSvg 模块**:少了它所有图标变空白(`QIcon` 是空的,按钮上什么都不画);
- **svg 图片格式插件**:少了它样式表里的复选框/单选框指示器加载失败 → 勾选框变空白;
- **cryptography 的 Ed25519 后端**:签发与验签全链路(含"异机指纹被拒绝");
- **orjson**(编译扩展):申请码编解码往返;
- **sqlite3 + 台账**:入库/检索/统计;
- **PostgreSQL 驱动(可选功能)**:构建输出里会打印一行 `PostgreSQL 驱动:可用/不可用`;
另有一个显式的 `CHONGMING_LICENSE_PG` 开关,给了才会**真连一次**库并跑一遍台账
适配层(在事务里,跑完回滚,不留痕迹);
- **tomllib + 路径处理**:插件自动发现;
- **主窗口装配**:四个页签、表格 9 列、结论横幅、整窗渲染出非空位图。
自检**不碰真实数据**:QSettings 被重定向到临时 INI、台账库指向临时文件
(否则 `RegistryTab` 初始化会去动你真实的 `~/.chongming/licenses.db`)。
它也不会弹窗 —— 全程不调用 `show()`,只在内存里渲染。
手动跑:
```bash
dist/pyinstaller/chongming-license-gui.exe --selftest --selftest-report 报告.txt
CHONGMING_LICENSE_PG="postgresql://user:pw@host:5432/db" \
dist/pyinstaller/chongming-license-gui.exe --selftest # 额外验一次真库
```
## 踩过的坑:PATH 上的同名 DLL 会污染产物
**症状**:exe 能启动、界面正常,但 PostgreSQL 功能报
`ImportError: no pq wrapper available`,往深看是
`DLL load failed while importing _ssl: 找不到指定的程序`。
**原因**:Windows 上 `libssl-3-x64.dll` / `libcrypto-3-x64.dll` 这类**通用名 DLL**
到处都是 —— Git for Windows、msys64、各种 SDK、厂商工具目录。它们都在 PATH 上。
PyInstaller 解析二进制依赖时会搜 PATH,**搜到哪份就用哪份**,于是把
`_ssl.pyd` 需要的 OpenSSL 换成了别的构建:
| 来源 | 大小 |
| --- | --- |
| Python 自带(正确) | 1,579,008 字节 |
| **Git for Windows**(被误收集) | **930,460 字节** |
| msys64 | 1,055,094 字节 |
版本对不上,`_ssl.pyd` 绑定符号失败 → 依赖 `_ssl` 的 psycopg 二进制实现整块不可用。
**修法**:`_common.build_env_overrides()` 在构建前清一遍 PATH ——
凡是**含有与 Python 自带 DLL 同名文件**的目录一律临时摘掉(系统目录永远保留),
并打印摘掉了哪些,两个构建脚本都会用。本机实测一次摘掉了 Git、msys64、
`E:\polyos_qemu`、ShadowBot,以及 PySide6 自己的目录。
> 这类问题的教训:**报错点离原因很远**(psycopg 说"没有 pq 实现",
> 实际是 OpenSSL 版本不对)。所以构建后自检必须验到"驱动能真的连上库",
> 光验"模块能 import"是不够的。
## 实现要点
```
packaging/
├── launcher.py # 冻结入口(用绝对导入,不用 __main__.py 的相对导入)
├── pyinstaller.spec # PyInstaller 配置(选项经环境变量传入)
├── build_pyinstaller.py # 驱动 + 守卫 + 自检
├── build_nuitka.py # 驱动 + 守卫 + 自检
└── _common.py # 图标生成 / 私钥扫描 / PATH 清理 / 自检驱动(两个脚本共用)
```
- **入口用 `launcher.py` 而不是 `__main__.py`**:后者是 `from .app import main` 这样的
包内相对导入,被冻结工具当普通脚本编译时会因失去包上下文而失败。
- **用源码树而不是 editable 安装**:spec 里 `pathex=src/`、Nuitka 里设 `PYTHONPATH=src/`。
冻结工具对 `.pth` 式 editable 安装的解析并不可靠。
- **图标是构建时生成的**:`_common.build_app_icon()` 把主题里的盾牌 SVG 渲染成
7 个尺寸的 `.ico`(自己拼 ICO 容器 —— Qt 的 ICO 写入只支持单张图,小尺寸会糊),
写完立刻回读校验。所以仓库里不需要存任何美术素材。
- **关掉 UPX**:UPX 压缩容易触发杀软误报,也可能损坏 Qt DLL。
- **裁掉用不到的 Qt 模块**:PySide6 装了整套 Addons(Qt3D/WebEngine/Charts…),
spec 里显式排除了一批;Nuitka 侧由 `pyside6` 插件负责,另有 `--nofollow-import-to`。
- **构建前清理 PATH**(见上一节):挡住同名 DLL 污染产物。
- **Optional 依赖**:PostgreSQL 支持是 `[project.optional-dependencies] postgres`,
但 dev 依赖组里也列了 psycopg —— 这样 `uv sync --group dev` 出来的环境打出来的包
**自带 PG 支持**;不做 PG 的用户 `uv sync` 也不会被强迫装驱动。
## 分发提醒
- 本工具是**厂商侧工具**,含签发能力,只发厂商内部同事;**不要给客户**,也不要放进部署产物。
- 打包产物里的界面截图、示例数据都来自 `tools/screenshot.py` 的演示数据,不含真实客户信息。
- exe **未做代码签名**,同事首次运行可能遇到 SmartScreen 提示("更多信息 → 仍要运行")。
要么让 IT 走内部白名单,要么买代码签名证书后给 exe 签名。
## 常见问题
**双击没反应 / 闪退?**
用 `--console` 重新打一个,命令行里运行看报错。多数是缺平台插件(自检能提前发现)。
**杀软报毒?**
PyInstaller 单文件的自解压行为常被启发式判成可疑。改用 `--onedir`,或换 Nuitka。
**Nuitka 报找不到 C 编译器?**
装 Visual Studio 的「使用 C++ 的桌面开发」工作负载;或用 `--mingw64`
(需 Nuitka 支持的 mingw64 版本 —— msys2 自带的那个通常不被接受)。
**Nuitka 报 `fatal error C1002` / `LNK1257`(编译器堆空间不足)?**
Nuitka 为每个模块生成一个上万行的 C 文件,MSVC 编译最大的那个时会耗尽编译器内部
堆空间。本项目的踩坑记录:
- 不带 `--low-memory` 时崩在 `chongming_license_gui.registry_tab`;
- 加上 `--low-memory` 后能过,但**引入 psycopg 后又会崩在 `psycopg.errors`**
(该模块定义了几百个异常类,生成 C 文件 2.8 万行,MSVC 单文件上限)。
这是 MSVC 的编译上限,不是代码问题。对策:
1. **优先用 PyInstaller 出带 PostgreSQL 支持的包** —— 它不编译 C,没有这个限制,
本项目已验证可用;
2. 或者给 Nuitka 换编译器:`--mingw64`(Nuitka 会自己下载它支持的
WinLibs mingw64,msys2 自带的那份不被接受);
3. 只在不需要 PG 时才用默认的 MSVC + `--low-memory`。
脚本默认已带 `--low-memory`(不带必崩),检测到该错误码还会自动重试一次。
**Nuitka 提示 "Please install the English language pack for Visual Studio"?**
中文版 VS 的报错文本是 GBK,Nuitka 解析不了(只影响日志可读性,不影响构建)。
装个英文语言包,或忽略它、直接看 `packaging/build/nuitka-build.log`。
**Nuitka 提示 "Please install the English language pack for Visual Studio"?**
中文版 VS 的报错文本是 GBK,Nuitka 解析不了(只影响日志可读性,不影响构建)。
装个英文语言包,或忽略它、直接看 `packaging/build/nuitka-build.log`。
**体积能不能再小?**
`--onedir` 后再自行压缩分发;或继续往 spec 的 `EXCLUDES` 里加确定用不到的模块。
单文件模式会把所有东西解压到临时目录,体积换的是分发便利。
**Nuitka 构建太慢?**
时间主要花在两处:一是 C 编译(Nuitka 的缓存目录会保留中间产物,第二次快很多),
二是 **LTO 链接**(实测单这一步就 6 分钟以上)。加 `--lto=no` 可显著缩短,
代价是产物略大、优化略少。另外 `.py` 文件改动越多,重新编译的文件越多。
**能不能打包 Linux / macOS 版?**
可以,但要在目标系统上构建(冻结产物不跨平台)。`packaging/_common.py` 里的
原生平台名已覆盖 `cocoa` / `xcb`,自检会自动按平台跑。注意本工具的硬件指纹
来源全是 Linux 侧的,打包目标平台与授权对象设备无关。