1
0

fix: Windows 桌面应用打包问题修复

- 删除通用 desktop target,重命名 platform targets 为简短形式 (desktop-mac/win/linux)
- 构建产物文件名统一为 nex-{os}-{arch}[.exe] 格式
- Windows 托盘图标使用 .ico 格式(运行时按平台选择)
- Windows 原生对话框使用 user32.MessageBoxW 替代 msg * 命令
- 更新 README.md 和 package-macos.sh 中的引用
- 添加单元测试覆盖 MessageBoxW 封装和图标选择逻辑
- 同步更新 desktop-app spec 规范文档
This commit is contained in:
2026-04-22 23:20:39 +08:00
parent 15f08ee2ca
commit 64dc66afa6
12 changed files with 370 additions and 33 deletions

View File

@@ -0,0 +1,81 @@
## Context
Nex 桌面应用是一个将后端服务Go/Gin与前端静态资源embed.FS打包为单一可执行文件的跨平台应用。当前 Windows 构建存在三类问题:
1. **通用 `desktop` target 无平台感知**:输出 `build/nex``.exe` 后缀,且缺少 `-H=windowsgui` linker flag 导致控制台窗口闪现
2. **系统托盘图标加载失败**`getlantern/systray` 在 Windows 上期望 ICO 格式,代码传入了 64x64 的 PNG
3. **`showError`/`showAbout` 使用 `msg *`**Windows Home 版本可能不可用,配合 `-H=windowsgui` 后行为不可预测,且不支持自定义标题栏
项目已有 `assets/icon.ico`256x256但代码未使用。
## Goals / Non-Goals
**Goals:**
- Windows 构建产物可直接双击运行(.exe 后缀、无控制台窗口)
- 系统托盘图标在所有平台上正确加载
- Windows 上使用原生 `MessageBoxW` 对话框替代 `msg *`
- Makefile target 命名简洁统一
**Non-Goals:**
- 不引入新的第三方依赖
- 不改变 macOS/Linux 上的现有行为
- 不涉及应用签名或代码公证(属于发布流程)
- 不重构整体打包架构
## Decisions
### 1. 删除通用 `desktop` target重命名平台 target
**决策**:删除 `desktop` target`desktop-darwin`/`desktop-windows`/`desktop-linux` 重命名为 `desktop-mac`/`desktop-win`/`desktop-linux`
**理由**:通用 target 在跨平台构建时必然需要条件判断,增加复杂度。按平台分离更明确,且项目已有先例。短命名 `win`/`mac`/`linux` 更简洁。
**产物命名统一**`nex-{os}-{arch}[.exe]`
- `nex-mac-arm64``nex-mac-amd64`
- `nex-win-amd64.exe`
- `nex-linux-amd64`
### 2. 托盘图标运行时按平台选择格式
**决策**:在 `setupSystray` 中根据 `runtime.GOOS` 选择图标文件:
- Windows加载 `assets/icon.ico`256x256 ICO
- 其他:加载 `assets/icon.png`PNG
**备选方案**
- ~~Build tags + 编译时选择~~:增加文件数,维护成本高
- ~~所有平台统一用 ICO~~Linux/macOS 的 systray 实现对 ICO 支持不一致
**理由**:运行时判断最简单,两个文件都已通过 `embedfs.Assets``assets/*`)嵌入,零额外成本。
### 3. Windows 原生对话框使用 `user32.MessageBoxW`
**决策**:通过 `syscall` 调用 `user32.dll``MessageBoxW`,替换 `msg *`
**实现方式**
```go
var (
user32 = syscall.NewLazyDLL("user32.dll")
procMessageBoxW = user32.NewProc("MessageBoxW")
)
func messageBox(title, message string) {
procMessageBoxW.Call(0, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(message))),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(title))), 0x10)
}
```
**备选方案**
- ~~继续用 `msg *`~~:不解决 Home 版不可用、标题栏不支持的问题
- ~~`rundll32` 调用~~:同样不可靠
- ~~引入 `lxn/walk` 等 GUI 库~~:引入重依赖,过度
**理由**`MessageBoxW` 是 Windows 原生 API所有版本都有`-H=windowsgui` 完美兼容,支持标题栏和图标类型,零依赖。使用 `syscall`(非 `unsafe` 外部依赖)即可。
### 4. `showError`/`showAbout` 统一用平台 switch
**决策**:保持现有的 `switch runtime.GOOS` 结构,仅替换 Windows 分支实现。macOSosascript和 Linuxzenity不变。
## Risks / Trade-offs
- **[syscall 跨架构]** `MessageBoxW``syscall.NewLazyDLL` 仅在 Windows 上有效 → 使用 `runtime.GOOS` 守卫,非 Windows 不会执行该路径,编译时通过 build 文件或运行时判断确保不触发
- **[ICO 嵌入体积]** `icon.ico` 270KB已在 `embedfs` 中,不增加新体积 → 无风险
- **[Makefile 兼容性]** 删除 `desktop` target 后CI/本地脚本如果引用它需更新 → 需检查是否有外部引用