feat: 增加 Client Windows 打包基础 (#92)
This commit is contained in:
@@ -111,7 +111,7 @@ client/data/
|
||||
└── artifacts/ 失败截图和控件树 XML
|
||||
```
|
||||
|
||||
跑源码时它就在 `client\data\`;将来打包成 exe 后,它在 `launcher.exe` 旁边。
|
||||
跑源码时它就在 `client\data\`;打包成 exe 后,它在根目录 `Launcher.exe` 旁边。
|
||||
|
||||
想看里面的数据,装一个免费工具 **DB Browser for SQLite**,用它打开 `client.db` 即可。表结构和每个字段什么意思,见 [03 数据模型](03-data-model.md)。
|
||||
|
||||
@@ -148,7 +148,20 @@ Remove-Item Env:QT_QPA_PLATFORM
|
||||
|
||||
第 2 条打印出 `OK` 就算通过。完整的验证要求见 [client/AGENTS.md](../../client/AGENTS.md) §验证。
|
||||
|
||||
## 10. 接下来读什么
|
||||
## 10. 打包 Windows 便携版
|
||||
|
||||
在 `client` 目录双击 `build_client.bat`,或者从 PowerShell 执行:
|
||||
|
||||
```powershell
|
||||
cd D:\chengma\cmautobuy\client
|
||||
.\build_client.bat
|
||||
```
|
||||
|
||||
第一次会在 `.build-venv/` 安装固定版本依赖,完成后产物位于 `release/`。发布时
|
||||
使用完整便携压缩包;`autobuy——manifest.json` 和更新压缩包留给版本发布与后续
|
||||
更新功能使用。不要把本机已有的 `data/` 塞进发布包。
|
||||
|
||||
## 11. 接下来读什么
|
||||
|
||||
不用一次读完全部文档。按你要做的事挑:
|
||||
|
||||
|
||||
@@ -183,25 +183,35 @@ one-file 每次启动都要解压到临时目录,启动慢,出错几乎没
|
||||
|
||||
```text
|
||||
CMAutoBuy/ 整个文件夹拷到任何机器都能用
|
||||
├── launcher.exe 双击这个启动
|
||||
├── app/ 运行时依赖,用户不要动
|
||||
│ ├── python310.dll
|
||||
│ ├── PyQt5/Qt5/
|
||||
│ │ ├── bin/ Qt5Core.dll / Qt5Gui.dll / Qt5Widgets.dll …
|
||||
│ │ └── plugins/platforms/qwindows.dll
|
||||
│ ├── qfluentwidgets/ qss 样式、字体、图标资源
|
||||
│ ├── adbutils/binaries/ adb.exe
|
||||
│ └── …
|
||||
├── Launcher.exe 双击这个启动;它只负责启动主程序
|
||||
├── app/ 主程序和运行时依赖,升级时整体替换
|
||||
│ ├── CMAutoBuy.exe
|
||||
│ ├── version.txt
|
||||
│ └── dependencies/
|
||||
│ ├── python310.dll
|
||||
│ ├── PyQt5/Qt5/
|
||||
│ │ ├── bin/ Qt5Core.dll / Qt5Gui.dll / Qt5Widgets.dll …
|
||||
│ │ └── plugins/platforms/qwindows.dll
|
||||
│ ├── qfluentwidgets/ qss 样式、字体、图标资源
|
||||
│ ├── adbutils/binaries/adb.exe
|
||||
│ └── …
|
||||
└── data/ 本地数据,升级时保留(见 [03 数据模型](03-data-model.md) §2.1)
|
||||
├── client.db
|
||||
├── logs/
|
||||
└── artifacts/
|
||||
```
|
||||
|
||||
`app/` 这个名字来自 PyInstaller 的 `--contents-directory app` 参数(默认叫 `_internal`)。
|
||||
主程序使用 PyInstaller one-dir,内部依赖目录通过 `--contents-directory dependencies`
|
||||
固定命名。根目录的 `Launcher.exe` 是独立轻量启动器,为后续“关闭主程序后再替换
|
||||
`app/`”保留边界;当前版本不联网、不下载,也不自动更新。
|
||||
|
||||
**升级方式:** 删掉 `launcher.exe` 和 `app/`,换成新版本,**`data/` 原样不动**。
|
||||
数据库靠 `PRAGMA user_version` 自动迁移,见 [03](03-data-model.md) §2.3。
|
||||
**当前升级方式:** 关闭程序后只替换 `app/`,**`Launcher.exe` 和 `data/` 原样不动**。
|
||||
数据库靠 `PRAGMA user_version` 自动迁移,见 [03](03-data-model.md) §2.3。完整便携包
|
||||
用于首次安装;更新包只包含 `app/`,不得包含 `data/`。
|
||||
|
||||
每次构建在 `client/release/` 生成更新包、完整便携包和
|
||||
`autobuy——manifest.json`。清单至少记录版本、文件名、字节大小和 SHA256;SHA256
|
||||
用于发现下载损坏,不等同于发布者身份认证。联网更新和清单签名必须另建工单。
|
||||
|
||||
**已知风险点**(打包工单必须逐项验证):
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ data/
|
||||
|
||||
| 什么情况 | `data/` 在哪 |
|
||||
|---|---|
|
||||
| 打包成 exe 后 | `launcher.exe` 旁边(见 [01 需求](01-requirements.md) §8.1 的目录结构) |
|
||||
| 打包成 exe 后 | 根目录 `Launcher.exe` 旁边(见 [01 需求](01-requirements.md) §8.1 的目录结构) |
|
||||
| 直接跑源码 | `client/data/`(已在 `.gitignore` 里,不会被提交) |
|
||||
|
||||
这叫**便携模式**:整个程序文件夹拷到哪都能用,出问题把文件夹打包发出来就能复现。
|
||||
@@ -57,11 +57,13 @@ from pathlib import Path
|
||||
def data_dir() -> Path:
|
||||
"""可写数据目录:数据库、日志、截图都放这儿。
|
||||
|
||||
打包后 = launcher.exe 旁边的 data/
|
||||
打包后 = 根目录 Launcher.exe 旁边的 data/
|
||||
跑源码 = client/data/
|
||||
"""
|
||||
if getattr(sys, "frozen", False): # frozen=True 说明是打包后的 exe
|
||||
base = Path(sys.executable).parent / "data"
|
||||
executable_dir = Path(sys.executable).resolve().parent
|
||||
install_root = executable_dir.parent if executable_dir.name.casefold() == "app" else executable_dir
|
||||
base = install_root / "data"
|
||||
else:
|
||||
base = Path(__file__).resolve().parents[1] / "data"
|
||||
base.mkdir(parents=True, exist_ok=True)
|
||||
@@ -71,7 +73,7 @@ def data_dir() -> Path:
|
||||
def app_dir() -> Path:
|
||||
"""只读资源目录:图标、内置 qss 之类,**不要往这里写东西**。
|
||||
|
||||
打包后 = app/ 目录(PyInstaller 解包位置)
|
||||
打包后 = app/dependencies/(PyInstaller 的只读依赖和收集资源目录)
|
||||
跑源码 = client/
|
||||
"""
|
||||
if getattr(sys, "frozen", False):
|
||||
@@ -106,7 +108,7 @@ PRAGMA busy_timeout = 5000;
|
||||
|
||||
使用 `PRAGMA user_version` 管理顺序迁移。数据库升级必须支持从所有已发布版本迁移,不得在启动时直接删除旧库重建。
|
||||
|
||||
这条在打包后尤其重要:升级 = 换掉 `launcher.exe` 和 `app/`,`data/` 原样保留,
|
||||
这条在打包后尤其重要:升级 = 换掉 `app/`,`Launcher.exe` 和 `data/` 原样保留,
|
||||
所以新版本必须能读旧数据库。
|
||||
|
||||
## 3. `pdd_tasks`
|
||||
|
||||
@@ -204,8 +204,9 @@ Artifact 写入前应脱敏,数据库只保存引用。保留周期由设置
|
||||
| 3 | UI 离屏冒烟测试通过 | 见 [client/AGENTS.md](../../client/AGENTS.md) §验证 第 2 条 | 开发者 |
|
||||
| 4 | 数据库从上一发布版本迁移成功 | 拿上一版本的 `client.db` 副本启动新版本,数据不丢 | 开发者 |
|
||||
| 5 | Mock Admin 契约测试通过 | Mock 和 HTTP 两个实现跑同一套测试 | 开发者 |
|
||||
| 6 | Windows 干净环境启动测试通过 | **未打包版本**:找一台没装过本项目的机器,照 [00 上手指南](00-getting-started.md) 从头走一遍。**已打包版本**:把整个文件夹拷到一台**没装 Python** 的机器上双击 `launcher.exe` | 开发者 |
|
||||
| 6b | 升级不丢数据(仅打包版本) | 换掉 `launcher.exe` 和 `app/`,`data/` 保留,启动后任务、日志、设置都还在 | 开发者 |
|
||||
| 6 | Windows 干净环境启动测试通过 | **未打包版本**:找一台没装过本项目的机器,照 [00 上手指南](00-getting-started.md) 从头走一遍。**已打包版本**:把整个文件夹拷到一台**没装 Python** 的机器上双击 `Launcher.exe` | 开发者 |
|
||||
| 6b | 升级不丢数据(仅打包版本) | 关闭程序并只换掉 `app/`,保留 `Launcher.exe` 和 `data/`;启动后任务、日志、设置都还在 | 开发者 |
|
||||
| 6c | 发布清单与产物一致 | `autobuy——manifest.json` 中的版本、文件名、字节大小和 SHA256 与实际压缩包一致,更新包中没有 `data/` | 开发者 |
|
||||
| 7 | 日志和产物无敏感信息 | 翻一遍日志和 `artifacts/`,确认没有 token、Cookie、密码、收货人信息 | 开发者 |
|
||||
| 8 | 开源和商业许可证已确认 | 新增依赖的许可证是否允许本项目的使用方式 | **项目负责人**(不是开发者自己判断) |
|
||||
| 9 | 真实下单版本额外满足采购安全门禁 | 逐条核对 §3 的 8 项 | **项目负责人 + 操作人员共同确认** |
|
||||
|
||||
Reference in New Issue
Block a user