1482 lines
39 KiB
Markdown
1482 lines
39 KiB
Markdown
# ChatLab 桌面应用打包技术方案
|
||
|
||
本文档总结当前项目的桌面化打包方式,并抽象成一套后续新项目可以复用的技术方案。当前项目采用的是“Web 应用 + 本地后端服务 + Electron 外壳 + 安装包生成器”的路线:业务仍按前后端分离开发,发布时把前端静态资源、Python 后端可执行文件、第三方命令行工具和运行时 DLL 一起放入 Electron 安装包中,最终交付一个 Windows 桌面应用安装程序。
|
||
|
||
## 1. 当前项目概览
|
||
|
||
### 1.1 项目定位
|
||
|
||
当前项目是一个本地化运行的 ChatLab 售后智能助手,主要能力是读取本机 PC 微信聊天记录,结合 AI 对售后群消息进行检索、话题分析、知识文档生成和知识库管理。
|
||
|
||
系统运行在客户本机,核心数据不需要上传到外部服务器。用户看到的是一个桌面应用窗口,底层实际由多个本地服务协同完成:
|
||
|
||
| 模块 | 路径 | 技术栈 | 作用 |
|
||
|---|---|---|---|
|
||
| 桌面壳 | `electron-launcher/` | Electron | 提供桌面窗口、启动页、进程管理、IPC 通信、安装包配置 |
|
||
| 前端页面 | `chatlab-web/frontend/` | React + Vite | 提供聊天记录、AI 分析、知识库、设置等界面 |
|
||
| 业务后端 | `chatlog_fastAPI/` | Python + FastAPI | 统一业务接口、AI 调用、数据库、任务调度、静态资源托管 |
|
||
| 微信数据服务 | `chatlog.exe` | Go 可执行文件 | 读取本机微信数据,提供 `127.0.0.1:5030` 的 chatlog API |
|
||
| 本地 DLL | `lib/windows_x64/wx_key.dll` | Windows DLL | 配合微信数据密钥识别 |
|
||
| 构建脚本 | `scripts/build-desktop.ps1` | PowerShell | 串联图标、前端、后端、资源复制、安装包构建和签名校验 |
|
||
|
||
### 1.2 当前桌面化结果
|
||
|
||
当前项目已经生成 Windows 安装包,输出目录为:
|
||
|
||
```text
|
||
release/
|
||
```
|
||
|
||
当前目录下可见的安装包包括:
|
||
|
||
```text
|
||
release/ChatLab-Setup-1.0.0.exe
|
||
release/ChatLab-Setup-1.0.1-202605210454.exe
|
||
```
|
||
|
||
安装包由 `electron-builder` 生成,Windows 安装器类型为 NSIS。安装后用户可以通过桌面快捷方式或开始菜单启动 `ChatLab售后智能助手`。
|
||
|
||
## 2. 当前项目使用的打包方法
|
||
|
||
### 2.1 总体思路
|
||
|
||
当前项目没有把 Web 应用重写成原生桌面应用,而是保留原来的 Web 架构:
|
||
|
||
1. React 前端继续用 Vite 构建成静态资源。
|
||
2. FastAPI 后端继续提供 HTTP API,并在生产环境托管前端静态文件。
|
||
3. `chatlog.exe` 作为本地外部二进制程序,由 Electron 主进程启动和管理。
|
||
4. Electron 只负责桌面窗口、启动控制、子进程生命周期、资源路径适配和安装包能力。
|
||
5. PyInstaller 把 Python 后端打成独立的 Windows 可执行目录。
|
||
6. electron-builder 把 Electron 主程序、前端构建产物、后端可执行目录、`chatlog.exe`、DLL 和许可文件一起封装为安装包。
|
||
|
||
可以把它理解成:
|
||
|
||
```text
|
||
用户双击桌面应用
|
||
|
|
||
v
|
||
Electron 启动
|
||
|
|
||
+--> 启动 FastAPI 后端,随机选择本地端口
|
||
|
|
||
+--> 启动 chatlog.exe,固定提供 127.0.0.1:5030
|
||
|
|
||
+--> 等待 /health 和 chatlog API 就绪
|
||
|
|
||
v
|
||
Electron 窗口加载 FastAPI 地址
|
||
|
|
||
v
|
||
FastAPI 返回 React 静态页面,页面再调用本地 API
|
||
```
|
||
|
||
### 2.2 关键设计点
|
||
|
||
#### 2.2.1 Electron 不是业务后端,只是桌面运行容器
|
||
|
||
`electron-launcher/main.js` 中的主进程负责:
|
||
|
||
- 创建桌面窗口。
|
||
- 根据 `app.isPackaged` 区分开发环境和打包环境。
|
||
- 在开发环境从源码目录启动 Python 后端。
|
||
- 在打包环境从 `resources/backend/ChatLabBackend.exe` 启动后端。
|
||
- 启动 `chatlog.exe` 并传入微信账号、密钥、工作目录等参数。
|
||
- 给 FastAPI 注入 `CHATLAB_DATA_DIR`、`CHATLAB_STATIC_DIR`、`CHATLAB_BACKEND_PORT` 等环境变量。
|
||
- 等待后端 `/health` 和 chatlog 服务就绪。
|
||
- 加载后端地址作为最终业务界面。
|
||
- 应用关闭时清理 FastAPI 和 chatlog 子进程。
|
||
|
||
这种方式的好处是:Electron 不承载复杂业务逻辑,业务逻辑仍然留在原来的 Python 后端和 React 前端里,后续维护成本低。
|
||
|
||
#### 2.2.2 前端打成静态文件,由 FastAPI 托管
|
||
|
||
开发时,前端通过 Vite 开发服务器运行:
|
||
|
||
```text
|
||
chatlab-web/frontend/
|
||
```
|
||
|
||
构建时执行:
|
||
|
||
```powershell
|
||
npm run build
|
||
```
|
||
|
||
产物位于:
|
||
|
||
```text
|
||
chatlab-web/frontend/dist/
|
||
```
|
||
|
||
打包脚本会把这个目录复制到:
|
||
|
||
```text
|
||
electron-launcher/build-resources/frontend/
|
||
```
|
||
|
||
Electron 安装包内的资源结构最终类似:
|
||
|
||
```text
|
||
resources/
|
||
frontend/
|
||
index.html
|
||
assets/
|
||
index-xxxx.js
|
||
index-xxxx.css
|
||
```
|
||
|
||
FastAPI 通过 `CHATLAB_STATIC_DIR` 知道前端静态资源路径,并在 `main.py` 中挂载:
|
||
|
||
- `/assets`
|
||
- `/favicon.svg`
|
||
- `/icons.svg`
|
||
- `/`
|
||
- `/{full_path:path}` SPA fallback
|
||
|
||
所以生产环境下不再单独启动 Vite,也没有 `5173` 前端开发端口。用户访问到的是 FastAPI 托管出来的 React 页面。
|
||
|
||
#### 2.2.3 Python 后端用 PyInstaller 打成 onedir
|
||
|
||
后端入口是:
|
||
|
||
```text
|
||
chatlog_fastAPI/run_backend.py
|
||
```
|
||
|
||
这个入口读取环境变量 `CHATLAB_BACKEND_PORT`,然后启动 Uvicorn:
|
||
|
||
```python
|
||
uvicorn.run(app, host="127.0.0.1", port=port, reload=False, log_level="info")
|
||
```
|
||
|
||
PyInstaller 配置文件是:
|
||
|
||
```text
|
||
chatlog_fastAPI/ChatLabBackend.spec
|
||
```
|
||
|
||
当前配置重点包括:
|
||
|
||
- 入口脚本:`run_backend.py`
|
||
- 输出名称:`ChatLabBackend`
|
||
- 模式:`COLLECT`,即 onedir 目录形式
|
||
- 控制台:`console=True`
|
||
- 收集 `jieba` 数据文件
|
||
- 显式收集 `uvicorn`、`fastapi`、`pydantic_settings`、`aiosqlite`、`apscheduler` 等 hidden imports
|
||
|
||
构建命令为:
|
||
|
||
```powershell
|
||
py -3.12 -m PyInstaller ChatLabBackend.spec --noconfirm --clean
|
||
```
|
||
|
||
构建后生成:
|
||
|
||
```text
|
||
chatlog_fastAPI/dist/ChatLabBackend/
|
||
ChatLabBackend.exe
|
||
_internal/
|
||
...
|
||
```
|
||
|
||
打包脚本会把该目录复制到:
|
||
|
||
```text
|
||
electron-launcher/build-resources/backend/
|
||
```
|
||
|
||
Electron 打包后,运行时后端路径就是:
|
||
|
||
```text
|
||
resources/backend/ChatLabBackend.exe
|
||
```
|
||
|
||
#### 2.2.4 外部程序和 DLL 作为 extraResources 随安装包发布
|
||
|
||
当前项目必须依赖:
|
||
|
||
```text
|
||
chatlog.exe
|
||
lib/windows_x64/wx_key.dll
|
||
```
|
||
|
||
这些不是 Electron 的 JS 文件,也不应该被打进 asar 包里。因此当前项目使用 `electron-builder` 的 `extraResources`,把它们作为普通文件复制到安装目录的 `resources/` 下。
|
||
|
||
打包前的临时资源目录为:
|
||
|
||
```text
|
||
electron-launcher/build-resources/
|
||
```
|
||
|
||
脚本会写入:
|
||
|
||
```text
|
||
electron-launcher/build-resources/
|
||
chatlog.exe
|
||
lib/
|
||
windows_x64/
|
||
wx_key.dll
|
||
frontend/
|
||
backend/
|
||
DISCLAIMER.md
|
||
LICENSE
|
||
```
|
||
|
||
`electron-builder.config.cjs` 中通过 `extraResources` 把整个 `build-resources` 复制到安装包资源目录:
|
||
|
||
```js
|
||
extraResources: [
|
||
{
|
||
from: path.join(__dirname, 'build-resources'),
|
||
to: '.',
|
||
filter: [
|
||
'**/*',
|
||
'!**/.env',
|
||
'!**/knowledge*.db',
|
||
'!**/__pycache__/**',
|
||
'!**/*.pfx',
|
||
'!**/*.p12',
|
||
'!**/*.pvk',
|
||
'!**/*.cer',
|
||
'!**/*.crt',
|
||
'!**/*.key',
|
||
'!**/certs/**',
|
||
],
|
||
},
|
||
]
|
||
```
|
||
|
||
这里的原则是:运行期必须作为真实文件存在的内容,都走 `extraResources`,不要放进 Electron 的 `files` 或 asar 内。
|
||
|
||
#### 2.2.5 数据目录放到用户 AppData,不写入安装目录
|
||
|
||
Electron 启动后端时设置:
|
||
|
||
```text
|
||
CHATLAB_DATA_DIR=%APPDATA%/ChatLab
|
||
```
|
||
|
||
FastAPI 后端通过 `config.py` 读取该目录,数据库默认放在类似路径:
|
||
|
||
```text
|
||
%APPDATA%/ChatLab/data/knowledge.db
|
||
```
|
||
|
||
这样做有几个优点:
|
||
|
||
- 安装目录通常没有写权限,避免运行时报权限错误。
|
||
- 用户数据与程序文件分离,方便升级安装包。
|
||
- 卸载、迁移、备份时路径清晰。
|
||
- 不会把客户数据误打进安装包。
|
||
|
||
后续新项目也应该遵循这个原则:安装目录只放程序文件,用户数据放 `%APPDATA%/应用名`、`%LOCALAPPDATA%/应用名` 或用户显式选择的数据目录。
|
||
|
||
#### 2.2.6 Electron 主进程负责端口和进程生命周期
|
||
|
||
当前项目中:
|
||
|
||
- FastAPI 端口由 Electron 动态寻找空闲端口。
|
||
- `chatlog.exe` 固定使用 `127.0.0.1:5030`。
|
||
- Electron 先启动服务,再轮询健康检查。
|
||
- 窗口关闭时通过 `taskkill /pid /f /t` 清理 Windows 子进程树。
|
||
|
||
这个设计解决了桌面应用常见的几个问题:
|
||
|
||
- 用户不需要自己打开命令行。
|
||
- 后端端口冲突概率降低。
|
||
- 后端启动失败时可以在启动页显示日志。
|
||
- 关闭桌面应用时不会残留后台进程。
|
||
|
||
## 3. 当前项目的构建入口
|
||
|
||
### 3.1 一键构建命令
|
||
|
||
当前桌面版构建入口为:
|
||
|
||
```powershell
|
||
powershell -ExecutionPolicy Bypass -File .\scripts\build-desktop.ps1
|
||
```
|
||
|
||
该脚本默认执行完整构建:
|
||
|
||
1. 生成或刷新 Electron 图标。
|
||
2. 构建 React 前端。
|
||
3. 用 PyInstaller 构建 Python 后端。
|
||
4. 重置 `electron-launcher/build-resources`。
|
||
5. 复制 `chatlog.exe`、`lib`、前端 `dist`、后端 `dist/ChatLabBackend`、许可文件。
|
||
6. 扫描敏感文件,阻止 `.env`、`knowledge*.db`、证书、私钥、缓存等进入发布资源。
|
||
7. 生成 `release/manifest.txt`。
|
||
8. 调用 `electron-builder` 生成 Windows NSIS 安装包。
|
||
9. 将安装包和 blockmap 复制到 `release/`。
|
||
10. 如启用签名,校验安装包签名状态。
|
||
|
||
### 3.2 可选参数
|
||
|
||
脚本支持跳过部分步骤,便于调试:
|
||
|
||
```powershell
|
||
powershell -ExecutionPolicy Bypass -File .\scripts\build-desktop.ps1 -SkipIcon
|
||
powershell -ExecutionPolicy Bypass -File .\scripts\build-desktop.ps1 -SkipFrontend
|
||
powershell -ExecutionPolicy Bypass -File .\scripts\build-desktop.ps1 -SkipBackend
|
||
powershell -ExecutionPolicy Bypass -File .\scripts\build-desktop.ps1 -SkipInstaller
|
||
```
|
||
|
||
常见用途:
|
||
|
||
| 参数 | 用途 |
|
||
|---|---|
|
||
| `-SkipIcon` | 图标没有变化时跳过图标生成 |
|
||
| `-SkipFrontend` | 前端没有变化时跳过 Vite 构建 |
|
||
| `-SkipBackend` | 后端没有变化时跳过 PyInstaller |
|
||
| `-SkipInstaller` | 只准备 `build-resources`,不生成安装包 |
|
||
|
||
### 3.3 代码签名构建
|
||
|
||
未签名安装包可以用于本地测试,但客户电脑上容易触发 Windows SmartScreen 或杀毒软件提示。正式交付建议使用 Windows 代码签名证书。
|
||
|
||
命令行方式:
|
||
|
||
```powershell
|
||
powershell -ExecutionPolicy Bypass -File .\scripts\build-desktop.ps1 `
|
||
-Sign `
|
||
-CertificateFile "D:\certs\ChatLab-CodeSigning.pfx" `
|
||
-CertificatePassword "证书密码" `
|
||
-PublisherName "证书中的发布者名称" `
|
||
-ForceSign
|
||
```
|
||
|
||
环境变量方式:
|
||
|
||
```powershell
|
||
$env:CHATLAB_PFX_FILE = "D:\certs\ChatLab-CodeSigning.pfx"
|
||
$env:CHATLAB_PFX_PASSWORD = "证书密码"
|
||
$env:CHATLAB_CERT_PUBLISHER_NAME = "证书中的发布者名称"
|
||
$env:CHATLAB_FORCE_SIGN = "1"
|
||
powershell -ExecutionPolicy Bypass -File .\scripts\build-desktop.ps1
|
||
```
|
||
|
||
签名相关变量:
|
||
|
||
| 变量 | 说明 |
|
||
|---|---|
|
||
| `CHATLAB_PFX_FILE` | PFX/P12 证书完整路径 |
|
||
| `CHATLAB_PFX_PASSWORD` | 证书密码 |
|
||
| `CHATLAB_CERT_PUBLISHER_NAME` | 可选,发布者名称 |
|
||
| `CHATLAB_TIMESTAMP_SERVER` | 可选,默认 `http://timestamp.digicert.com` |
|
||
| `CHATLAB_FORCE_SIGN` | 设置为 `1` 后签名失败会中断构建 |
|
||
|
||
证书安全要求:
|
||
|
||
- 证书不要放进项目目录。
|
||
- 证书不要放进 `build-resources`。
|
||
- 证书密码不要写进代码仓库。
|
||
- 构建脚本已经阻止 `.pfx`、`.p12`、`.pvk`、`.key`、`.cer`、`.crt`、`certs/` 进入发布资源。
|
||
|
||
## 4. 目录与产物说明
|
||
|
||
### 4.1 开发源码目录
|
||
|
||
```text
|
||
get_wechat_me/
|
||
chatlab-web/
|
||
frontend/
|
||
src/
|
||
public/
|
||
dist/
|
||
package.json
|
||
vite.config.js
|
||
|
||
chatlog_fastAPI/
|
||
main.py
|
||
run_backend.py
|
||
config.py
|
||
requirements.txt
|
||
ChatLabBackend.spec
|
||
routers/
|
||
services/
|
||
dist/
|
||
|
||
electron-launcher/
|
||
main.js
|
||
preload.js
|
||
index.html
|
||
package.json
|
||
electron-builder.config.cjs
|
||
build/
|
||
build-resources/
|
||
dist/
|
||
|
||
lib/
|
||
windows_x64/
|
||
wx_key.dll
|
||
|
||
scripts/
|
||
build-desktop.ps1
|
||
make-icon.cjs
|
||
|
||
chatlog.exe
|
||
release/
|
||
```
|
||
|
||
### 4.2 构建过程中产生的关键目录
|
||
|
||
| 目录 | 生成者 | 作用 |
|
||
|---|---|---|
|
||
| `chatlab-web/frontend/dist` | Vite | 前端静态资源 |
|
||
| `chatlog_fastAPI/build` | PyInstaller | Python 打包中间产物 |
|
||
| `chatlog_fastAPI/dist/ChatLabBackend` | PyInstaller | Python 后端可执行目录 |
|
||
| `electron-launcher/build` | 图标脚本 | Electron 图标 |
|
||
| `electron-launcher/build-resources` | 构建脚本 | 准备给 electron-builder 的额外资源 |
|
||
| `electron-launcher/dist` | electron-builder | win-unpacked 和安装包 |
|
||
| `release` | 构建脚本 | 最终交付产物目录 |
|
||
|
||
### 4.3 安装包内的运行时结构
|
||
|
||
安装后大致结构如下:
|
||
|
||
```text
|
||
安装目录/
|
||
ChatLab售后智能助手.exe
|
||
resources/
|
||
app.asar 或 app/
|
||
main.js
|
||
preload.js
|
||
index.html
|
||
package.json
|
||
|
||
backend/
|
||
ChatLabBackend.exe
|
||
_internal/
|
||
|
||
frontend/
|
||
index.html
|
||
assets/
|
||
|
||
chatlog.exe
|
||
lib/
|
||
windows_x64/
|
||
wx_key.dll
|
||
DISCLAIMER.md
|
||
LICENSE
|
||
```
|
||
|
||
Electron 主进程通过 `process.resourcesPath` 找到 `resources/`,再拼出 `backend/ChatLabBackend.exe`、`frontend/`、`chatlog.exe` 和 `lib/` 的绝对路径。
|
||
|
||
## 5. 当前方案为什么适合这个项目
|
||
|
||
### 5.1 适合多技术栈项目
|
||
|
||
当前项目同时包含:
|
||
|
||
- React 前端
|
||
- Python FastAPI 后端
|
||
- Go 编译好的 `chatlog.exe`
|
||
- Windows DLL
|
||
- 本地 SQLite 数据
|
||
- AI API 配置
|
||
|
||
如果强行改成单一技术栈,成本很高。Electron 的好处是可以把这些组件“原样收纳”进桌面应用,同时保留已有前后端开发方式。
|
||
|
||
### 5.2 适合本地化交付
|
||
|
||
客户只需要安装一个桌面程序,不需要手动安装 Python、Node.js、前端依赖、后端依赖,也不需要知道命令行怎么启动。
|
||
|
||
打包后:
|
||
|
||
- Python 运行时随 PyInstaller 产物携带。
|
||
- 前端 JS/CSS 已经静态化。
|
||
- Electron 内置 Chromium,不依赖客户电脑浏览器。
|
||
- chatlog 和 DLL 随资源文件一起分发。
|
||
- 用户数据写入 AppData,不污染安装目录。
|
||
|
||
### 5.3 适合需要启动多个本地服务的应用
|
||
|
||
这个项目不是单页面离线工具,而是必须启动本地后端和微信数据服务。Electron 主进程天然适合做“进程管家”:
|
||
|
||
- 启动服务。
|
||
- 显示日志。
|
||
- 检查健康状态。
|
||
- 控制按钮状态。
|
||
- 关闭时清理进程。
|
||
- 在异常时给用户可理解的提示。
|
||
|
||
## 6. 新项目复用方案
|
||
|
||
后续如果要把新的 Web + 后端项目打包成桌面应用,可以按下面方案实施。
|
||
|
||
### 6.1 推荐技术选型
|
||
|
||
| 层级 | 推荐方案 | 说明 |
|
||
|---|---|---|
|
||
| 桌面壳 | Electron | 最适合承载已有 Web 应用和本地子进程 |
|
||
| 安装包 | electron-builder | 支持 NSIS、图标、快捷方式、签名、extraResources |
|
||
| 前端 | Vite / React / Vue 等 | 构建成静态资源即可 |
|
||
| Python 后端 | PyInstaller onedir | 对 FastAPI、依赖库、本地数据文件支持较好 |
|
||
| Node 后端 | 直接作为 Electron 子进程或打包为 pkg/nexe | 视项目复杂度选择 |
|
||
| Go/Rust 后端 | 直接编译 exe 后作为 extraResources | 最简单稳定 |
|
||
| 本地数据 | AppData / LocalAppData | 不写安装目录 |
|
||
| 进程通信 | HTTP + IPC | 业务走 HTTP,桌面控制走 Electron IPC |
|
||
|
||
### 6.2 标准目录模板
|
||
|
||
建议新项目从一开始就按下面结构组织:
|
||
|
||
```text
|
||
new-project/
|
||
frontend/
|
||
package.json
|
||
vite.config.js
|
||
src/
|
||
dist/
|
||
|
||
backend/
|
||
main.py
|
||
run_backend.py
|
||
requirements.txt
|
||
Backend.spec
|
||
dist/
|
||
|
||
desktop/
|
||
main.js
|
||
preload.js
|
||
index.html
|
||
package.json
|
||
electron-builder.config.cjs
|
||
build/
|
||
build-resources/
|
||
dist/
|
||
|
||
native-tools/
|
||
your-tool.exe
|
||
dlls/
|
||
|
||
scripts/
|
||
build-desktop.ps1
|
||
|
||
release/
|
||
```
|
||
|
||
如果项目没有 Python 后端,可以删除 `backend/` 和 PyInstaller 步骤。如果项目没有外部二进制工具,可以删除 `native-tools/`。
|
||
|
||
### 6.3 新项目落地步骤
|
||
|
||
#### 第一步:明确桌面应用运行方式
|
||
|
||
先回答下面几个问题:
|
||
|
||
| 问题 | 建议 |
|
||
|---|---|
|
||
| 前端是否需要联网? | 如果只是本地业务,优先由本地后端托管静态资源 |
|
||
| 后端是否必须存在? | 有数据库、AI、文件、系统调用时建议保留本地后端 |
|
||
| 是否需要外部 exe/DLL? | 需要真实文件的工具统一放入 `extraResources` |
|
||
| 是否需要安装包? | 正式交付建议使用 NSIS 安装包 |
|
||
| 是否需要代码签名? | 客户交付建议签名 |
|
||
| 用户数据放在哪里? | 放 AppData,不放安装目录 |
|
||
|
||
对当前项目这类本地服务型应用,推荐运行方式为:
|
||
|
||
```text
|
||
Electron -> 启动本地后端 -> 后端托管前端 -> Electron 加载后端 URL
|
||
```
|
||
|
||
#### 第二步:前端适配生产环境
|
||
|
||
前端要满足:
|
||
|
||
- 能通过 `npm run build` 生成静态资源。
|
||
- 页面路由支持 SPA fallback。
|
||
- API 请求尽量使用相对路径,例如 `/api/xxx`,不要硬编码 `http://127.0.0.1:5173`。
|
||
- 开发环境可以通过 Vite proxy 转发 API。
|
||
- 生产环境由本地后端托管静态文件并处理 API。
|
||
|
||
示例 `vite.config.js`:
|
||
|
||
```js
|
||
import { defineConfig } from 'vite'
|
||
import react from '@vitejs/plugin-react'
|
||
|
||
export default defineConfig({
|
||
plugins: [react()],
|
||
server: {
|
||
port: 5173,
|
||
strictPort: true,
|
||
host: '127.0.0.1',
|
||
proxy: {
|
||
'/api': { target: 'http://127.0.0.1:8000', changeOrigin: true },
|
||
},
|
||
},
|
||
})
|
||
```
|
||
|
||
生产构建命令:
|
||
|
||
```powershell
|
||
cd frontend
|
||
npm install
|
||
npm run build
|
||
```
|
||
|
||
#### 第三步:后端适配桌面运行
|
||
|
||
后端要满足:
|
||
|
||
- 能从环境变量读取端口。
|
||
- 只监听 `127.0.0.1`,避免暴露到局域网。
|
||
- 提供 `/health` 健康检查。
|
||
- 能从环境变量读取数据目录。
|
||
- 能从环境变量读取静态资源目录。
|
||
- 生产环境托管前端 `dist`。
|
||
- 不依赖当前工作目录查找文件,尽量使用绝对路径或环境变量路径。
|
||
|
||
示例 `run_backend.py`:
|
||
|
||
```python
|
||
import os
|
||
import uvicorn
|
||
from main import app
|
||
|
||
|
||
def main():
|
||
port = int(os.environ.get("APP_BACKEND_PORT", "8000"))
|
||
uvicorn.run(app, host="127.0.0.1", port=port, reload=False, log_level="info")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|
||
```
|
||
|
||
示例静态资源托管:
|
||
|
||
```python
|
||
import os
|
||
from pathlib import Path
|
||
from fastapi import FastAPI
|
||
from fastapi.responses import FileResponse
|
||
from fastapi.staticfiles import StaticFiles
|
||
|
||
app = FastAPI()
|
||
|
||
static_dir = Path(os.environ.get("APP_STATIC_DIR", "frontend/dist"))
|
||
|
||
if static_dir.exists():
|
||
assets_dir = static_dir / "assets"
|
||
if assets_dir.exists():
|
||
app.mount("/assets", StaticFiles(directory=str(assets_dir)), name="assets")
|
||
|
||
@app.get("/", include_in_schema=False)
|
||
async def index():
|
||
return FileResponse(static_dir / "index.html")
|
||
|
||
@app.get("/{full_path:path}", include_in_schema=False)
|
||
async def spa_fallback(full_path: str):
|
||
target = static_dir / full_path
|
||
if target.exists() and target.is_file():
|
||
return FileResponse(target)
|
||
return FileResponse(static_dir / "index.html")
|
||
```
|
||
|
||
#### 第四步:配置 PyInstaller
|
||
|
||
创建 `Backend.spec`,最小示例:
|
||
|
||
```python
|
||
# -*- mode: python ; coding: utf-8 -*-
|
||
|
||
a = Analysis(
|
||
["run_backend.py"],
|
||
pathex=[],
|
||
binaries=[],
|
||
datas=[],
|
||
hiddenimports=[],
|
||
hookspath=[],
|
||
hooksconfig={},
|
||
runtime_hooks=[],
|
||
excludes=[],
|
||
noarchive=False,
|
||
optimize=0,
|
||
)
|
||
pyz = PYZ(a.pure)
|
||
|
||
exe = EXE(
|
||
pyz,
|
||
a.scripts,
|
||
[],
|
||
exclude_binaries=True,
|
||
name="Backend",
|
||
console=True,
|
||
)
|
||
|
||
coll = COLLECT(
|
||
exe,
|
||
a.binaries,
|
||
a.datas,
|
||
strip=False,
|
||
upx=True,
|
||
name="Backend",
|
||
)
|
||
```
|
||
|
||
如果后端使用 FastAPI、Uvicorn、APScheduler、jieba、Pydantic Settings 等动态导入库,需要显式收集 hidden imports 或 data files:
|
||
|
||
```python
|
||
from PyInstaller.utils.hooks import collect_data_files, collect_submodules
|
||
|
||
datas = []
|
||
datas += collect_data_files("jieba")
|
||
|
||
hiddenimports = []
|
||
hiddenimports += collect_submodules("uvicorn")
|
||
hiddenimports += collect_submodules("fastapi")
|
||
hiddenimports += collect_submodules("pydantic_settings")
|
||
hiddenimports += collect_submodules("apscheduler")
|
||
```
|
||
|
||
构建命令:
|
||
|
||
```powershell
|
||
cd backend
|
||
py -3.12 -m PyInstaller Backend.spec --noconfirm --clean
|
||
```
|
||
|
||
建议优先使用 onedir,而不是 onefile:
|
||
|
||
| 模式 | 优点 | 缺点 | 建议 |
|
||
|---|---|---|---|
|
||
| onedir | 启动快,依赖文件清晰,问题好排查 | 文件多 | 桌面应用内置后端优先使用 |
|
||
| onefile | 单个 exe 好看 | 启动慢,会解压临时文件,杀软误报概率更高 | 只适合很小的工具 |
|
||
|
||
#### 第五步:创建 Electron 壳
|
||
|
||
`desktop/package.json` 示例:
|
||
|
||
```json
|
||
{
|
||
"name": "your-app-desktop",
|
||
"version": "1.0.0",
|
||
"main": "main.js",
|
||
"scripts": {
|
||
"start": "electron .",
|
||
"build": "electron-builder --win --config electron-builder.config.cjs"
|
||
},
|
||
"devDependencies": {
|
||
"electron": "^42.0.0",
|
||
"electron-builder": "^26.8.1"
|
||
}
|
||
}
|
||
```
|
||
|
||
`desktop/main.js` 需要包含这些核心能力:
|
||
|
||
- 资源路径适配。
|
||
- 创建窗口。
|
||
- 启动后端。
|
||
- 等待健康检查。
|
||
- 加载业务页面。
|
||
- 关闭时清理子进程。
|
||
|
||
路径适配示例:
|
||
|
||
```js
|
||
const { app, BrowserWindow } = require('electron')
|
||
const path = require('path')
|
||
|
||
function isPackaged() {
|
||
return app.isPackaged
|
||
}
|
||
|
||
function projectRoot() {
|
||
return isPackaged() ? process.resourcesPath : path.resolve(__dirname, '..')
|
||
}
|
||
|
||
function resourcePath(...parts) {
|
||
return path.join(projectRoot(), ...parts)
|
||
}
|
||
|
||
function backendExePath() {
|
||
return resourcePath('backend', 'Backend.exe')
|
||
}
|
||
|
||
function frontendDistDir() {
|
||
return isPackaged()
|
||
? resourcePath('frontend')
|
||
: resourcePath('frontend', 'dist')
|
||
}
|
||
```
|
||
|
||
启动后端示例:
|
||
|
||
```js
|
||
const { spawn } = require('child_process')
|
||
const net = require('net')
|
||
const http = require('http')
|
||
|
||
let backendPort = null
|
||
let backendUrl = null
|
||
let backendProcess = null
|
||
|
||
function getFreePort() {
|
||
return new Promise((resolve, reject) => {
|
||
const server = net.createServer()
|
||
server.listen(0, '127.0.0.1', () => {
|
||
const port = server.address().port
|
||
server.close(() => resolve(port))
|
||
})
|
||
server.on('error', reject)
|
||
})
|
||
}
|
||
|
||
async function startBackend() {
|
||
if (backendProcess) return
|
||
|
||
backendPort = backendPort || await getFreePort()
|
||
backendUrl = `http://127.0.0.1:${backendPort}`
|
||
|
||
const env = {
|
||
...process.env,
|
||
APP_BACKEND_PORT: String(backendPort),
|
||
APP_STATIC_DIR: frontendDistDir(),
|
||
APP_DATA_DIR: path.join(app.getPath('appData'), 'YourApp'),
|
||
}
|
||
|
||
const command = isPackaged() ? backendExePath() : 'python'
|
||
const args = isPackaged() ? [] : ['run_backend.py']
|
||
const cwd = isPackaged() ? path.dirname(command) : resourcePath('backend')
|
||
|
||
backendProcess = spawn(command, args, { cwd, env, windowsHide: true, shell: !isPackaged() })
|
||
}
|
||
```
|
||
|
||
窗口加载策略:
|
||
|
||
```js
|
||
async function openAppWindow() {
|
||
await startBackend()
|
||
await waitForHealth()
|
||
mainWindow.loadURL(backendUrl)
|
||
}
|
||
```
|
||
|
||
#### 第六步:配置 preload 和 IPC
|
||
|
||
如果启动页需要按钮控制后端、显示日志、触发刷新等,不要在渲染进程直接开启 Node 能力。推荐:
|
||
|
||
- `nodeIntegration: false`
|
||
- `contextIsolation: true`
|
||
- 通过 `preload.js` 暴露有限 API
|
||
|
||
示例:
|
||
|
||
```js
|
||
const { contextBridge, ipcRenderer } = require('electron')
|
||
|
||
contextBridge.exposeInMainWorld('desktopAPI', {
|
||
startAll: () => ipcRenderer.invoke('start-all'),
|
||
getStatus: () => ipcRenderer.invoke('get-status'),
|
||
onLog: (callback) => ipcRenderer.on('log', (_event, value) => callback(value)),
|
||
})
|
||
```
|
||
|
||
主进程:
|
||
|
||
```js
|
||
const { ipcMain } = require('electron')
|
||
|
||
ipcMain.handle('start-all', async () => {
|
||
await openAppWindow()
|
||
return { ok: true, backendUrl }
|
||
})
|
||
```
|
||
|
||
#### 第七步:配置 electron-builder
|
||
|
||
示例 `desktop/electron-builder.config.cjs`:
|
||
|
||
```js
|
||
const path = require('path')
|
||
|
||
module.exports = {
|
||
appId: 'com.company.yourapp',
|
||
productName: 'YourApp',
|
||
icon: 'build/icon.ico',
|
||
directories: {
|
||
output: 'dist',
|
||
},
|
||
files: [
|
||
'main.js',
|
||
'preload.js',
|
||
'index.html',
|
||
'build/icon.ico',
|
||
'build/icon.png',
|
||
'package.json',
|
||
],
|
||
extraResources: [
|
||
{
|
||
from: path.join(__dirname, 'build-resources'),
|
||
to: '.',
|
||
filter: [
|
||
'**/*',
|
||
'!**/.env',
|
||
'!**/*.db',
|
||
'!**/__pycache__/**',
|
||
'!**/*.pfx',
|
||
'!**/*.p12',
|
||
'!**/*.key',
|
||
'!**/certs/**',
|
||
],
|
||
},
|
||
],
|
||
win: {
|
||
target: 'nsis',
|
||
icon: 'build/icon.ico',
|
||
artifactName: 'YourApp-Setup-${version}.${ext}',
|
||
},
|
||
nsis: {
|
||
oneClick: false,
|
||
allowToChangeInstallationDirectory: true,
|
||
perMachine: false,
|
||
createDesktopShortcut: true,
|
||
createStartMenuShortcut: true,
|
||
shortcutName: 'YourApp',
|
||
},
|
||
}
|
||
```
|
||
|
||
要点:
|
||
|
||
- `files` 只放 Electron 自己运行所需的 JS/HTML/图标/package。
|
||
- `extraResources` 放后端 exe、前端 dist、外部工具、DLL、模板文件等真实资源。
|
||
- 敏感文件必须通过 filter 排除。
|
||
- 正式应用应设置稳定的 `appId`。
|
||
- `productName` 会影响安装目录、快捷方式和应用显示名。
|
||
|
||
#### 第八步:编写统一构建脚本
|
||
|
||
建议新项目也使用一个 PowerShell 脚本串联所有步骤。伪代码如下:
|
||
|
||
```powershell
|
||
$ErrorActionPreference = "Stop"
|
||
|
||
$Root = Resolve-Path (Join-Path $PSScriptRoot "..")
|
||
$Frontend = Join-Path $Root "frontend"
|
||
$Backend = Join-Path $Root "backend"
|
||
$Desktop = Join-Path $Root "desktop"
|
||
$Resources = Join-Path $Desktop "build-resources"
|
||
$Release = Join-Path $Root "release"
|
||
|
||
# 1. 构建前端
|
||
Push-Location $Frontend
|
||
npm.cmd run build
|
||
Pop-Location
|
||
|
||
# 2. 构建后端
|
||
Push-Location $Backend
|
||
py -3.12 -m PyInstaller Backend.spec --noconfirm --clean
|
||
Pop-Location
|
||
|
||
# 3. 重置资源目录
|
||
if (Test-Path $Resources) {
|
||
Remove-Item -LiteralPath (Resolve-Path $Resources).Path -Recurse -Force
|
||
}
|
||
New-Item -ItemType Directory -Force -Path $Resources | Out-Null
|
||
|
||
# 4. 复制资源
|
||
Copy-Item -LiteralPath (Join-Path $Frontend "dist") -Destination (Join-Path $Resources "frontend") -Recurse -Force
|
||
Copy-Item -LiteralPath (Join-Path $Backend "dist\Backend") -Destination (Join-Path $Resources "backend") -Recurse -Force
|
||
Copy-Item -LiteralPath (Join-Path $Root "native-tools\your-tool.exe") -Destination (Join-Path $Resources "your-tool.exe") -Force
|
||
|
||
# 5. 生成安装包
|
||
Push-Location $Desktop
|
||
npm.cmd run build
|
||
Pop-Location
|
||
|
||
# 6. 复制到 release
|
||
New-Item -ItemType Directory -Force -Path $Release | Out-Null
|
||
Copy-Item -Path (Join-Path $Desktop "dist\*.exe") -Destination $Release -Force
|
||
```
|
||
|
||
当前项目的 `scripts/build-desktop.ps1` 已经是一份更完整的版本,包含:
|
||
|
||
- Python 版本回退逻辑。
|
||
- 证书路径校验。
|
||
- 环境变量签名参数。
|
||
- 安全删除目录。
|
||
- 敏感文件扫描。
|
||
- release manifest。
|
||
- 签名校验。
|
||
|
||
新项目建议直接以它为蓝本改名、改路径、改资源即可。
|
||
|
||
## 7. 当前项目构建链路详解
|
||
|
||
### 7.1 构建链路
|
||
|
||
```text
|
||
scripts/build-desktop.ps1
|
||
|
|
||
+--> scripts/make-icon.cjs
|
||
| |
|
||
| +--> electron-launcher/build/icon.ico
|
||
| +--> electron-launcher/build/icon.png
|
||
|
|
||
+--> chatlab-web/frontend
|
||
| |
|
||
| +--> npm run build
|
||
| +--> chatlab-web/frontend/dist
|
||
|
|
||
+--> chatlog_fastAPI
|
||
| |
|
||
| +--> py -3.12 -m PyInstaller ChatLabBackend.spec
|
||
| +--> chatlog_fastAPI/dist/ChatLabBackend
|
||
|
|
||
+--> electron-launcher/build-resources
|
||
| |
|
||
| +--> chatlog.exe
|
||
| +--> lib/
|
||
| +--> frontend/
|
||
| +--> backend/
|
||
| +--> LICENSE / DISCLAIMER.md
|
||
|
|
||
+--> electron-launcher
|
||
| |
|
||
| +--> npm run build
|
||
| +--> electron-builder --win --config electron-builder.config.cjs
|
||
|
|
||
+--> release/
|
||
|
|
||
+--> ChatLab-Setup-版本号-构建标识.exe
|
||
+--> ChatLab-Setup-版本号-构建标识.exe.blockmap
|
||
+--> manifest.txt
|
||
```
|
||
|
||
### 7.2 运行链路
|
||
|
||
```text
|
||
ChatLab售后智能助手.exe
|
||
|
|
||
v
|
||
Electron main.js
|
||
|
|
||
+--> createWindow()
|
||
| |
|
||
| +--> loadFile("index.html")
|
||
| +--> 显示启动控制页
|
||
|
|
||
+--> 用户点击启动 / start-all
|
||
|
|
||
+--> startBackend()
|
||
| |
|
||
| +--> 查找空闲端口
|
||
| +--> 设置 CHATLAB_DATA_DIR
|
||
| +--> 设置 CHATLAB_STATIC_DIR
|
||
| +--> 设置 CHATLAB_BACKEND_PORT
|
||
| +--> 启动 resources/backend/ChatLabBackend.exe
|
||
| +--> 等待 /health
|
||
|
|
||
+--> startChatlog()
|
||
| |
|
||
| +--> 检查 PC 微信进程
|
||
| +--> 执行 chatlog.exe key --force
|
||
| +--> 读取 ~/.chatlog/chatlog.json
|
||
| +--> 启动 chatlog.exe server --auto-decrypt ...
|
||
| +--> 等待 127.0.0.1:5030 API 就绪
|
||
|
|
||
+--> mainWindow.loadURL(backendUrl)
|
||
|
|
||
+--> FastAPI 返回 React 页面
|
||
```
|
||
|
||
## 8. 新项目可复用的技术规范
|
||
|
||
### 8.1 资源路径规范
|
||
|
||
必须区分开发环境和打包环境:
|
||
|
||
| 场景 | 根目录 |
|
||
|---|---|
|
||
| 开发环境 | 项目源码根目录 |
|
||
| 打包环境 | `process.resourcesPath` |
|
||
|
||
不要在 Electron 主进程中写死源码路径。所有资源路径都通过类似函数统一生成:
|
||
|
||
```js
|
||
function projectRoot() {
|
||
return app.isPackaged ? process.resourcesPath : path.resolve(__dirname, '..')
|
||
}
|
||
|
||
function resourcePath(...parts) {
|
||
return path.join(projectRoot(), ...parts)
|
||
}
|
||
```
|
||
|
||
### 8.2 端口规范
|
||
|
||
推荐:
|
||
|
||
- 后端端口由 Electron 动态分配。
|
||
- 对外只监听 `127.0.0.1`。
|
||
- 前端生产环境使用相对路径调用 API。
|
||
- 必须提供 `/health`。
|
||
- 启动页等待 `/health` 成功后再进入系统。
|
||
|
||
如果有必须固定端口的外部工具,要在启动前检查端口占用,并给出明确错误提示。
|
||
|
||
### 8.3 数据目录规范
|
||
|
||
桌面应用不要把用户数据写入安装目录。建议:
|
||
|
||
```text
|
||
%APPDATA%/应用名/
|
||
data/
|
||
logs/
|
||
cache/
|
||
config/
|
||
```
|
||
|
||
后端通过环境变量读取:
|
||
|
||
```text
|
||
APP_DATA_DIR
|
||
```
|
||
|
||
Electron 负责传入:
|
||
|
||
```js
|
||
APP_DATA_DIR: path.join(app.getPath('appData'), 'YourApp')
|
||
```
|
||
|
||
### 8.4 日志规范
|
||
|
||
建议至少保留两类日志:
|
||
|
||
| 日志 | 位置 | 作用 |
|
||
|---|---|---|
|
||
| 启动页实时日志 | Electron IPC | 给用户和售后定位启动问题 |
|
||
| 文件日志 | AppData logs | 给开发定位线上问题 |
|
||
|
||
敏感信息必须脱敏,例如:
|
||
|
||
- API Key
|
||
- token
|
||
- 数据库密码
|
||
- 用户密钥
|
||
- 证书路径和密码
|
||
|
||
当前项目在 Electron 主进程里已经对 `apiKey`、`data_key`、`img_key`、`sk-xxxx` 做了日志脱敏。
|
||
|
||
### 8.5 安全规范
|
||
|
||
正式打包前必须检查:
|
||
|
||
- `.env` 不进入安装包。
|
||
- 测试数据库不进入安装包。
|
||
- 用户数据不进入安装包。
|
||
- 证书和私钥不进入安装包。
|
||
- `__pycache__` 不进入安装包。
|
||
- 日志文件不进入安装包。
|
||
- API Key 不写死到前端代码。
|
||
- Electron 渲染进程不启用 Node 权限。
|
||
|
||
当前项目的安全策略包括:
|
||
|
||
- `build-desktop.ps1` 构建前扫描 `build-resources` 和 `release`。
|
||
- `electron-builder.config.cjs` 在 `extraResources.filter` 中排除敏感文件。
|
||
- `preload.js` 通过 `contextBridge` 暴露有限 IPC。
|
||
- `mainWindow` 设置 `nodeIntegration: false`、`contextIsolation: true`。
|
||
|
||
### 8.6 签名规范
|
||
|
||
内部测试可以使用未签名包,但正式交付建议签名:
|
||
|
||
- 证书放项目外。
|
||
- 密码通过环境变量传入。
|
||
- 启用时间戳服务器。
|
||
- 启用 `forceCodeSigning` 或自定义强校验。
|
||
- 构建结束后用 `Get-AuthenticodeSignature` 校验。
|
||
|
||
Windows 签名不能完全消除 SmartScreen 提示,但可以显著降低拦截概率,并建立发布者信誉。
|
||
|
||
## 9. 验收测试方案
|
||
|
||
### 9.1 构建前检查
|
||
|
||
执行:
|
||
|
||
```powershell
|
||
node -v
|
||
npm -v
|
||
py -3.12 -V
|
||
```
|
||
|
||
检查:
|
||
|
||
- Node.js 可用。
|
||
- Python 3.12 可用。
|
||
- 前端依赖已安装。
|
||
- Electron 依赖已安装。
|
||
- Python 后端依赖已安装。
|
||
- `chatlog.exe` 存在。
|
||
- `lib/windows_x64/wx_key.dll` 存在。
|
||
- 图标文件可生成或已存在。
|
||
|
||
### 9.2 本地开发运行检查
|
||
|
||
开发模式建议分别检查:
|
||
|
||
```powershell
|
||
cd chatlab-web/frontend
|
||
npm run build
|
||
```
|
||
|
||
```powershell
|
||
cd chatlog_fastAPI
|
||
python run_backend.py
|
||
```
|
||
|
||
```powershell
|
||
cd electron-launcher
|
||
npm run start
|
||
```
|
||
|
||
重点看:
|
||
|
||
- Electron 启动页能打开。
|
||
- FastAPI 能启动。
|
||
- `/health` 返回正常。
|
||
- 前端静态页面能被后端托管。
|
||
- chatlog 服务能启动并返回群聊或会话数据。
|
||
|
||
### 9.3 打包检查
|
||
|
||
执行:
|
||
|
||
```powershell
|
||
powershell -ExecutionPolicy Bypass -File .\scripts\build-desktop.ps1
|
||
```
|
||
|
||
检查输出:
|
||
|
||
- `chatlab-web/frontend/dist` 已更新。
|
||
- `chatlog_fastAPI/dist/ChatLabBackend/ChatLabBackend.exe` 存在。
|
||
- `electron-launcher/build-resources/frontend` 存在。
|
||
- `electron-launcher/build-resources/backend/ChatLabBackend.exe` 存在。
|
||
- `electron-launcher/build-resources/chatlog.exe` 存在。
|
||
- `electron-launcher/build-resources/lib/windows_x64/wx_key.dll` 存在。
|
||
- `release/manifest.txt` 已生成。
|
||
- `release/ChatLab-Setup-*.exe` 已生成。
|
||
|
||
### 9.4 安装后功能检查
|
||
|
||
在干净 Windows 机器或虚拟机上安装:
|
||
|
||
1. 双击安装包。
|
||
2. 确认桌面快捷方式和开始菜单快捷方式生成。
|
||
3. 启动应用。
|
||
4. 确认启动页显示正常。
|
||
5. 点击启动或进入系统。
|
||
6. 确认 FastAPI 启动成功。
|
||
7. 确认 chatlog 启动成功。
|
||
8. 确认主界面加载成功。
|
||
9. 确认关闭窗口后无残留 `ChatLabBackend.exe` 和 `chatlog.exe`。
|
||
10. 确认用户数据写入 `%APPDATA%/ChatLab`,不是安装目录。
|
||
|
||
### 9.5 发布前安全检查
|
||
|
||
检查 `release/manifest.txt`,确认没有:
|
||
|
||
```text
|
||
.env
|
||
knowledge*.db
|
||
__pycache__
|
||
*.pfx
|
||
*.p12
|
||
*.pvk
|
||
*.cer
|
||
*.crt
|
||
*.key
|
||
certs/
|
||
```
|
||
|
||
签名包额外执行:
|
||
|
||
```powershell
|
||
Get-AuthenticodeSignature .\release\ChatLab-Setup-*.exe
|
||
```
|
||
|
||
期望:
|
||
|
||
```text
|
||
Status: Valid
|
||
```
|
||
|
||
## 10. 常见问题与处理方案
|
||
|
||
### 10.1 打包后前端空白
|
||
|
||
可能原因:
|
||
|
||
- 前端构建产物没有复制到 `build-resources/frontend`。
|
||
- FastAPI 没有正确读取 `CHATLAB_STATIC_DIR`。
|
||
- React 路由没有 SPA fallback。
|
||
- 生产环境 API 地址仍写死为开发端口。
|
||
|
||
处理:
|
||
|
||
- 检查安装目录 `resources/frontend/index.html` 是否存在。
|
||
- 检查 `resources/frontend/assets` 是否存在。
|
||
- 打开后端 `/health` 查看 `static_dir` 或日志。
|
||
- 前端 API 统一改为相对路径。
|
||
|
||
### 10.2 打包后后端启动失败
|
||
|
||
可能原因:
|
||
|
||
- PyInstaller hidden imports 不完整。
|
||
- 某些数据文件没有被 `collect_data_files` 收集。
|
||
- 后端依赖当前工作目录。
|
||
- 安装目录没有写权限。
|
||
|
||
处理:
|
||
|
||
- 先运行 `chatlog_fastAPI/dist/ChatLabBackend/ChatLabBackend.exe` 看报错。
|
||
- 检查 `warn-ChatLabBackend.txt`。
|
||
- 在 `.spec` 中增加 hidden imports 或 datas。
|
||
- 数据写入 AppData,不写程序目录。
|
||
|
||
### 10.3 Electron 找不到 exe 或 DLL
|
||
|
||
可能原因:
|
||
|
||
- 资源没有复制到 `build-resources`。
|
||
- 路径仍按源码目录查找。
|
||
- 资源被打入 asar,导致外部程序无法直接运行。
|
||
|
||
处理:
|
||
|
||
- 外部 exe/DLL 使用 `extraResources`。
|
||
- 使用 `process.resourcesPath` 拼运行时路径。
|
||
- 不要让外部可执行文件依赖 asar 内路径。
|
||
|
||
### 10.4 关闭应用后仍有后台进程
|
||
|
||
可能原因:
|
||
|
||
- 只 kill 了父进程,没有 kill 子进程树。
|
||
- Electron 退出流程没有等待清理。
|
||
|
||
处理:
|
||
|
||
- Windows 使用 `taskkill /pid <pid> /f /t`。
|
||
- `window.close` 和 `before-quit` 中都调用清理逻辑。
|
||
- 清理时维护进程句柄,不要只靠进程名杀。
|
||
|
||
### 10.5 客户电脑提示风险或拦截
|
||
|
||
可能原因:
|
||
|
||
- 安装包未签名。
|
||
- 新证书发布者信誉不足。
|
||
- PyInstaller 或 Electron 包体较大,被杀软谨慎处理。
|
||
- onefile 自解压行为更容易被误报。
|
||
|
||
处理:
|
||
|
||
- 使用代码签名证书。
|
||
- 使用 onedir 后端。
|
||
- 避免把测试工具、调试脚本、临时文件放进安装包。
|
||
- 给客户提供发布者、用途、安装路径说明。
|
||
|
||
### 10.6 中文应用名乱码
|
||
|
||
可能原因:
|
||
|
||
- 文件本身编码不是 UTF-8。
|
||
- PowerShell 控制台编码导致显示乱码。
|
||
- 构建环境 locale 不一致。
|
||
|
||
处理:
|
||
|
||
- 源码文件统一保存为 UTF-8。
|
||
- 构建脚本和配置文件避免混用编码。
|
||
- 如控制台显示乱码,但安装包内应用名正常,可优先检查实际安装结果。
|
||
|
||
## 11. 新项目复制当前方案时需要修改的清单
|
||
|
||
| 修改项 | 当前项目值 | 新项目需要改成 |
|
||
|---|---|---|
|
||
| 应用名 | `ChatLab售后智能助手` | 新项目产品名 |
|
||
| appId | `com.chatlab.desktop` | `com.公司名.应用名` |
|
||
| Electron 目录 | `electron-launcher` | 新项目 desktop 目录 |
|
||
| 前端目录 | `chatlab-web/frontend` | 新项目前端目录 |
|
||
| 后端目录 | `chatlog_fastAPI` | 新项目后端目录 |
|
||
| 后端 exe 名 | `ChatLabBackend.exe` | 新项目后端 exe 名 |
|
||
| 数据目录 | `%APPDATA%/ChatLab` | `%APPDATA%/新应用名` |
|
||
| 图标 | `electron-launcher/build/icon.ico` | 新项目图标 |
|
||
| 安装包名 | `ChatLab-Setup-${version}-${buildLabel}.exe` | 新项目安装包命名 |
|
||
| 额外资源 | `chatlog.exe`、`lib` | 新项目实际外部资源 |
|
||
| 敏感文件规则 | `.env`、`knowledge*.db`、证书等 | 按新项目补充 |
|
||
| 健康检查 | `/health` | 新项目健康检查接口 |
|
||
| 固定端口 | chatlog `5030` | 新项目实际需要 |
|
||
| 环境变量前缀 | `CHATLAB_` | 新项目独立前缀 |
|
||
|
||
## 12. 建议的最终构建命令手册
|
||
|
||
### 12.1 首次准备环境
|
||
|
||
```powershell
|
||
# 前端依赖
|
||
cd chatlab-web/frontend
|
||
npm install
|
||
|
||
# Electron 依赖
|
||
cd ../../electron-launcher
|
||
npm install
|
||
|
||
# Python 依赖
|
||
cd ../chatlog_fastAPI
|
||
py -3.12 -m pip install -r requirements.txt
|
||
py -3.12 -m pip install pyinstaller
|
||
```
|
||
|
||
### 12.2 本地测试构建
|
||
|
||
```powershell
|
||
cd 项目根目录
|
||
powershell -ExecutionPolicy Bypass -File .\scripts\build-desktop.ps1
|
||
```
|
||
|
||
### 12.3 只准备资源,不生成安装包
|
||
|
||
```powershell
|
||
powershell -ExecutionPolicy Bypass -File .\scripts\build-desktop.ps1 -SkipInstaller
|
||
```
|
||
|
||
### 12.4 前端没改,只重打后端和安装包
|
||
|
||
```powershell
|
||
powershell -ExecutionPolicy Bypass -File .\scripts\build-desktop.ps1 -SkipFrontend
|
||
```
|
||
|
||
### 12.5 后端没改,只重打前端和安装包
|
||
|
||
```powershell
|
||
powershell -ExecutionPolicy Bypass -File .\scripts\build-desktop.ps1 -SkipBackend
|
||
```
|
||
|
||
### 12.6 正式签名发布
|
||
|
||
```powershell
|
||
$env:CHATLAB_PFX_FILE = "D:\certs\ChatLab-CodeSigning.pfx"
|
||
$env:CHATLAB_PFX_PASSWORD = "证书密码"
|
||
$env:CHATLAB_CERT_PUBLISHER_NAME = "证书中的发布者名称"
|
||
$env:CHATLAB_FORCE_SIGN = "1"
|
||
|
||
powershell -ExecutionPolicy Bypass -File .\scripts\build-desktop.ps1
|
||
```
|
||
|
||
## 13. 结论
|
||
|
||
当前项目使用的是一套比较稳妥的桌面化工程方案:Electron 负责桌面体验和进程管理,React 负责界面,FastAPI 负责业务接口和静态资源托管,PyInstaller 负责 Python 后端二进制化,electron-builder 负责 Windows 安装包生成,`extraResources` 负责携带外部 exe、DLL 和构建产物。
|
||
|
||
这套方案的核心价值在于:不破坏原来的 Web 项目结构,又能把多进程、本地服务、外部工具和前端页面统一交付成一个用户可安装、可双击启动的桌面应用。后续新项目只要按照“前端静态化、后端可执行化、Electron 管理进程、资源走 extraResources、用户数据进 AppData、构建脚本统一编排”的原则,就可以复用当前项目的打包方法。
|
||
|