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:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-04-22
|
||||
81
openspec/changes/fix-windows-desktop-packaging/design.md
Normal file
81
openspec/changes/fix-windows-desktop-packaging/design.md
Normal 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 分支实现。macOS(osascript)和 Linux(zenity)不变。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **[syscall 跨架构]** `MessageBoxW` 的 `syscall.NewLazyDLL` 仅在 Windows 上有效 → 使用 `runtime.GOOS` 守卫,非 Windows 不会执行该路径,编译时通过 build 文件或运行时判断确保不触发
|
||||
- **[ICO 嵌入体积]** `icon.ico` 270KB,已在 `embedfs` 中,不增加新体积 → 无风险
|
||||
- **[Makefile 兼容性]** 删除 `desktop` target 后,CI/本地脚本如果引用它需更新 → 需检查是否有外部引用
|
||||
28
openspec/changes/fix-windows-desktop-packaging/proposal.md
Normal file
28
openspec/changes/fix-windows-desktop-packaging/proposal.md
Normal file
@@ -0,0 +1,28 @@
|
||||
## Why
|
||||
|
||||
Windows 桌面应用存在三个影响用户体验的问题:构建产物无 `.exe` 后缀无法双击运行、运行时弹出控制台窗口、系统托盘图标加载失败。此外 `showError`/`showAbout` 在 Windows 上使用 `msg *` 命令不可靠。这些问题导致应用在 Windows 上不够专业,需要统一修复。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 删除通用 `desktop` Makefile target,仅保留按平台分离的 target
|
||||
- Makefile target 重命名为简短形式:`desktop-win`、`desktop-mac`、`desktop-linux`
|
||||
- 构建产物文件名统一为 `nex-{os}-{arch}[.exe]` 格式
|
||||
- 系统托盘图标在 Windows 上使用 `.ico` 格式(运行时 `runtime.GOOS` 判断)
|
||||
- Windows `showError`/`showAbout` 改用 `user32.dll` 的 `MessageBoxW` 原生对话框
|
||||
- 同步更新已有 `desktop-app` spec 中的构建产物命名和图标格式要求
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
无
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `desktop-app`: 构建产物命名规范变更(`nex-{os}-{arch}`);Windows 托盘图标需使用 `.ico` 格式;Windows 原生对话框替代 `msg *` 命令
|
||||
|
||||
## Impact
|
||||
|
||||
- `Makefile`:删除 `desktop` target,重命名其余三个 target 和产物文件名
|
||||
- `backend/cmd/desktop/main.go`:修改 `setupSystray` 图标加载逻辑、`showError`/`showAbout` Windows 实现
|
||||
- `openspec/specs/desktop-app/spec.md`:更新构建产物命名和 Windows 图标格式要求
|
||||
@@ -0,0 +1,87 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 系统托盘
|
||||
|
||||
系统 SHALL 提供跨平台系统托盘功能,支持托盘图标和菜单。图标格式 SHALL 根据平台自动选择。
|
||||
|
||||
#### Scenario: 托盘图标显示
|
||||
- **WHEN** 桌面应用启动成功
|
||||
- **THEN** 系统根据平台加载正确的图标格式
|
||||
- **AND** 在 Windows 上加载 ICO 格式图标(`assets/icon.ico`)
|
||||
- **AND** 在 macOS 和 Linux 上加载 PNG 格式图标(`assets/icon.png`)
|
||||
- **AND** 托盘图标 tooltip 显示"AI Gateway"
|
||||
|
||||
#### Scenario: 托盘菜单显示
|
||||
- **WHEN** 用户点击托盘图标(左键或右键)
|
||||
- **THEN** 显示托盘菜单
|
||||
- **AND** 菜单包含"打开管理界面"选项
|
||||
- **AND** 菜单包含"状态: 运行中"选项(禁用状态)
|
||||
- **AND** 菜单包含"端口: 9826"选项(禁用状态)
|
||||
- **AND** 菜单包含"关于"选项
|
||||
- **AND** 菜单包含"退出"选项
|
||||
|
||||
#### Scenario: 打开管理界面
|
||||
- **WHEN** 用户点击托盘菜单"打开管理界面"
|
||||
- **THEN** 系统在浏览器中打开 `http://localhost:9826`
|
||||
|
||||
#### Scenario: 浏览器打开失败
|
||||
- **WHEN** 系统无法打开浏览器(浏览器未安装等)
|
||||
- **THEN** 托盘菜单仍可正常使用
|
||||
- **AND** 用户可手动访问 `http://localhost:9826`
|
||||
|
||||
#### Scenario: 退出应用
|
||||
- **WHEN** 用户点击托盘菜单"退出"
|
||||
- **THEN** 系统优雅关闭后端服务
|
||||
- **AND** 托盘图标消失
|
||||
- **AND** 应用进程退出
|
||||
|
||||
### Requirement: 跨平台构建
|
||||
|
||||
系统 SHALL 支持跨平台构建和打包。构建 target SHALL 按平台分离,产物文件名 SHALL 使用 `nex-{os}-{arch}[.exe]` 格式。
|
||||
|
||||
#### Scenario: macOS 构建
|
||||
- **WHEN** 执行 `desktop-mac` 构建命令
|
||||
- **THEN** 生成 `nex-mac-arm64` 和 `nex-mac-amd64` 可执行文件
|
||||
- **AND** 可打包为 `.app` bundle
|
||||
|
||||
#### Scenario: Windows 构建
|
||||
- **WHEN** 执行 `desktop-win` 构建命令
|
||||
- **THEN** 生成 `nex-win-amd64.exe` 可执行文件
|
||||
- **AND** 使用 `-H=windowsgui` linker flag 隐藏控制台窗口
|
||||
|
||||
#### Scenario: Linux 构建
|
||||
- **WHEN** 执行 `desktop-linux` 构建命令
|
||||
- **THEN** 生成 `nex-linux-amd64` 可执行文件
|
||||
|
||||
### Requirement: 关于对话框
|
||||
|
||||
系统 SHALL 提供关于对话框显示应用信息。在 Windows 上 SHALL 使用 `user32.dll` 的 `MessageBoxW` API 实现。
|
||||
|
||||
#### Scenario: 显示关于
|
||||
- **WHEN** 用户点击托盘菜单"关于"
|
||||
- **THEN** 显示对话框包含应用名称、项目链接
|
||||
- **AND** 在 Windows 上使用 `MessageBoxW` 原生对话框实现
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Windows 原生对话框
|
||||
|
||||
系统 SHALL 在 Windows 上使用 `user32.dll` 的 `MessageBoxW` API 显示错误和关于对话框,替代 `msg *` 命令。
|
||||
|
||||
#### Scenario: 错误提示对话框
|
||||
- **WHEN** 应用在 Windows 上遇到启动错误(端口占用、配置加载失败等)
|
||||
- **THEN** 使用 `MessageBoxW` 显示模态对话框
|
||||
- **AND** 对话框标题栏显示应用名称
|
||||
- **AND** 对话框包含错误描述文本
|
||||
- **AND** 对话框显示错误图标(MB_ICONERROR)
|
||||
|
||||
#### Scenario: 关于对话框
|
||||
- **WHEN** 用户在 Windows 上点击托盘菜单"关于"
|
||||
- **THEN** 使用 `MessageBoxW` 显示模态对话框
|
||||
- **AND** 对话框标题栏显示"关于 Nex Gateway"
|
||||
- **AND** 对话框包含应用信息文本
|
||||
- **AND** 对话框显示信息图标(MB_ICONINFORMATION)
|
||||
|
||||
#### Scenario: 非 Windows 平台不受影响
|
||||
- **WHEN** 应用运行在 macOS 或 Linux 上
|
||||
- **THEN** 错误和关于对话框仍使用平台原有实现(osascript / zenity)
|
||||
28
openspec/changes/fix-windows-desktop-packaging/tasks.md
Normal file
28
openspec/changes/fix-windows-desktop-packaging/tasks.md
Normal file
@@ -0,0 +1,28 @@
|
||||
## 1. Makefile 重构
|
||||
|
||||
- [x] 1.1 删除通用 `desktop` target 及其相关 `.PHONY` 声明
|
||||
- [x] 1.2 将 `desktop-darwin` 重命名为 `desktop-mac`,产物文件名改为 `nex-mac-arm64` 和 `nex-mac-amd64`
|
||||
- [x] 1.3 将 `desktop-windows` 重命名为 `desktop-win`,产物文件名改为 `nex-win-amd64.exe`
|
||||
- [x] 1.4 将 `desktop-linux` 产物文件名改为 `nex-linux-amd64`
|
||||
- [x] 1.5 更新 `.PHONY` 声明和 `all` target(如引用了旧名称)
|
||||
|
||||
## 2. Windows 原生对话框
|
||||
|
||||
- [x] 2.1 在 `backend/cmd/desktop/main.go` 中添加 Windows 平台的 `user32.MessageBoxW` 调用封装(`syscall.NewLazyDLL` + `syscall.StringToUTF16Ptr`),在 `showError`/`showAbout` 的 Windows `runtime.GOOS` 分支内直接调用
|
||||
- [x] 2.2 替换 `showError` 函数的 Windows 分支,使用 `MessageBoxW` 替代 `msg *`
|
||||
- [x] 2.3 替换 `showAbout` 函数的 Windows 分支,使用 `MessageBoxW` 替代 `msg *`
|
||||
|
||||
## 3. 系统托盘图标修复
|
||||
|
||||
- [x] 3.1 修改 `setupSystray` 函数中的图标加载逻辑,根据 `runtime.GOOS` 在 Windows 上加载 `assets/icon.ico`,其他平台加载 `assets/icon.png`
|
||||
|
||||
## 4. 文档和脚本更新
|
||||
|
||||
- [x] 4.1 更新 `README.md` 中的构建命令引用(`desktop-darwin` → `desktop-mac`,`desktop-windows` → `desktop-win`,`desktop-linux` 保持不变或改为 `desktop-linux`)
|
||||
- [x] 4.2 更新 `scripts/build/package-macos.sh` 中对 `desktop-darwin` 的引用
|
||||
|
||||
## 5. 测试验证
|
||||
|
||||
- [x] 5.1 为 `showError`/`showAbout` 的 Windows `MessageBoxW` 封装编写单元测试(验证参数传递和调用正确性)
|
||||
- [x] 5.2 为图标加载的平台选择逻辑编写单元测试(验证 Windows 选 `.ico`,其他选 `.png`)
|
||||
- [x] 5.3 运行 `make desktop-win` 在 Windows 上验证:产物有 `.exe` 后缀、双击无控制台窗口、托盘图标正常显示、错误对话框使用原生样式
|
||||
@@ -31,11 +31,13 @@ TBD - 提供跨平台桌面应用支持,将后端服务与前端静态资源
|
||||
|
||||
### Requirement: 系统托盘
|
||||
|
||||
系统 SHALL 提供跨平台系统托盘功能,支持托盘图标和菜单。
|
||||
系统 SHALL 提供跨平台系统托盘功能,支持托盘图标和菜单。图标格式 SHALL 根据平台自动选择。
|
||||
|
||||
#### Scenario: 托盘图标显示
|
||||
- **WHEN** 桌面应用启动成功
|
||||
- **THEN** 系统托盘区域显示应用图标
|
||||
- **THEN** 系统根据平台加载正确的图标格式
|
||||
- **AND** 在 Windows 上加载 ICO 格式图标(`assets/icon.ico`)
|
||||
- **AND** 在 macOS 和 Linux 上加载 PNG 格式图标(`assets/icon.png`)
|
||||
- **AND** 托盘图标 tooltip 显示"AI Gateway"
|
||||
|
||||
#### Scenario: 托盘菜单显示
|
||||
@@ -93,20 +95,20 @@ TBD - 提供跨平台桌面应用支持,将后端服务与前端静态资源
|
||||
|
||||
### Requirement: 跨平台构建
|
||||
|
||||
系统 SHALL 支持跨平台构建和打包。
|
||||
系统 SHALL 支持跨平台构建和打包。构建 target SHALL 按平台分离,产物文件名 SHALL 使用 `nex-{os}-{arch}[.exe]` 格式。
|
||||
|
||||
#### Scenario: macOS 构建
|
||||
- **WHEN** 执行 macOS 构建命令
|
||||
- **THEN** 生成 `nex-darwin-arm64` 和 `nex-darwin-amd64` 可执行文件
|
||||
- **WHEN** 执行 `desktop-mac` 构建命令
|
||||
- **THEN** 生成 `nex-mac-arm64` 和 `nex-mac-amd64` 可执行文件
|
||||
- **AND** 可打包为 `.app` bundle
|
||||
|
||||
#### Scenario: Windows 构建
|
||||
- **WHEN** 执行 Windows 构建命令
|
||||
- **THEN** 生成 `nex-windows-amd64.exe` 可执行文件
|
||||
- **AND** 运行时不显示控制台窗口
|
||||
- **WHEN** 执行 `desktop-win` 构建命令
|
||||
- **THEN** 生成 `nex-win-amd64.exe` 可执行文件
|
||||
- **AND** 使用 `-H=windowsgui` linker flag 隐藏控制台窗口
|
||||
|
||||
#### Scenario: Linux 构建
|
||||
- **WHEN** 执行 Linux 构建命令
|
||||
- **WHEN** 执行 `desktop-linux` 构建命令
|
||||
- **THEN** 生成 `nex-linux-amd64` 可执行文件
|
||||
|
||||
### Requirement: macOS .app 打包
|
||||
@@ -123,8 +125,31 @@ TBD - 提供跨平台桌面应用支持,将后端服务与前端静态资源
|
||||
|
||||
### Requirement: 关于对话框
|
||||
|
||||
系统 SHALL 提供关于对话框显示应用信息。
|
||||
系统 SHALL 提供关于对话框显示应用信息。在 Windows 上 SHALL 使用 `user32.dll` 的 `MessageBoxW` API 实现。
|
||||
|
||||
#### Scenario: 显示关于
|
||||
- **WHEN** 用户点击托盘菜单"关于"
|
||||
- **THEN** 显示对话框包含应用名称、项目链接
|
||||
- **AND** 在 Windows 上使用 `MessageBoxW` 原生对话框实现
|
||||
|
||||
### Requirement: Windows 原生对话框
|
||||
|
||||
系统 SHALL 在 Windows 上使用 `user32.dll` 的 `MessageBoxW` API 显示错误和关于对话框,替代 `msg *` 命令。
|
||||
|
||||
#### Scenario: 错误提示对话框
|
||||
- **WHEN** 应用在 Windows 上遇到启动错误(端口占用、配置加载失败等)
|
||||
- **THEN** 使用 `MessageBoxW` 显示模态对话框
|
||||
- **AND** 对话框标题栏显示应用名称
|
||||
- **AND** 对话框包含错误描述文本
|
||||
- **AND** 对话框显示错误图标(MB_ICONERROR)
|
||||
|
||||
#### Scenario: 关于对话框
|
||||
- **WHEN** 用户在 Windows 上点击托盘菜单"关于"
|
||||
- **THEN** 使用 `MessageBoxW` 显示模态对话框
|
||||
- **AND** 对话框标题栏显示"关于 Nex Gateway"
|
||||
- **AND** 对话框包含应用信息文本
|
||||
- **AND** 对话框显示信息图标(MB_ICONINFORMATION)
|
||||
|
||||
#### Scenario: 非 Windows 平台不受影响
|
||||
- **WHEN** 应用运行在 macOS 或 Linux 上
|
||||
- **THEN** 错误和关于对话框仍使用平台原有实现(osascript / zenity)
|
||||
|
||||
Reference in New Issue
Block a user