Files
dzc fb399ae8f6 fix(windows): 全量代码审查修复——中文乱码风险/Inno 兼容/ffi 布局自检/部署说明
系统性把 mac 踩过的坑逐一在 Windows 对应路径排查后修复四处:

1. runner 加 /utf-8 编译选项(高危):MSVC 默认按系统代码页(中文 Windows=GBK)
   解析 UTF-8 源码,main.cpp 的 L"资安信插件助手" 窗口标题会乱码
2. installer.iss 的 x64compatible → x64:前者需 Inno 6.3+,旧版 iscc 直接报错
3. PavoApi 构造加结构体布局 assert(80/32/16/48 字节,对齐 C# 实机验证值),
   Windows 首跑(debug)即暴露 ffi 对齐差异
4. README 补 Windows 部署注意:VC++ Redistributable(PavoApiW_x64.dll 可能
   动态依赖 vcruntime140.dll)、SmartScreen 未签名提示、Inno 6.x 要求

其余复查项确认无问题:reg 命令引号(空格路径安全)、Dart/Inno 的 Run 键名
一致(ZaxDesktopPlugin)、vendor DLL 拷贝用生成器表达式(Debug/Release 通用)、
pavo 全部 ffi 调用在 Isolate.run、scheme 注册表路径拼接、关窗拦截走
window_manager 的 WM_CLOSE 主路径(无 mac 的 _close: 私有 selector 问题)。
2026-09-21 14:22:16 +08:00

110 lines
8.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# desktop-plugin(资安信插件助手)
**资安信插件助手**:桌面插件集成软件(目录名 desktop-plugin,Dart 包名 desktop_plugin,bundle id com.zax.desktop-plugin,App 显示名"资安信插件助手")。当前内置第一个插件"饭卡打印机",后续插件在 `lib/ui/routes.dart` 注册即自动出现在左侧菜单。关闭窗口 = 最小化到后台托盘,服务持续运行;退出入口在托盘菜单。
用 Flutter 重写打印站客户端,替代 C# 版 card-printer-agent。HTTP 协议**完全不变**,前端(`biz-pc-cloud-zax/src/api/plugin/cardPrinter.ts`)零改动,两版助手可并存灰度替换。
- 图标源:资安信 logo(`biz_zax_app_cloud/unpackage/res/icons/1024x1024.png` 深蓝盾牌+白色人形);`dart run tool/gen_app_icons.dart <源图>` 重新生成 mac AppIcon/Windows ICO/托盘图标
- 总体计划见 `../card-printer-agent/flutter-client-plan.md`
## 协议(与 C# 版逐字段对齐)
监听 `127.0.0.1:38200`,被占用顺延 38201/38202;CORS `*`(含 OPTIONS 预检);响应统一 `{code, msg, data}`,code=0 成功;JSON 字段一律驼峰。
| 端点 | 说明 |
|---|---|
| `GET /manifest` | `{plugins:[…], agent:"desktop-plugin", backend:"pavo\|cups", mock}` |
| `GET /api/card-printer/status` | PrinterStatus(online/model/firmware/connection/ribbon/cardLow/cardLeft/status/statusText) |
| `POST /api/card-printer/print` | `{copies, front:{color,k?}, back:{color,k?}}` → `{taskId}` |
| `GET /api/card-printer/task/{taskId}` | `{taskId, state:printing\|done\|error, status, statusText}` |
任务串行执行(打印机物理一次一张),完成后保留 30 分钟。
## 目录结构
```
lib/
├── main.dart # Flutter 窗口入口(服务状态 + 打印机状态 + 运行日志)
├── agent.dart # 核心组合:参数解析 + 日志缓冲 + 后端/队列/服务装配(纯 Dart,无 Flutter 依赖;stop 幂等)
├── ui/
│ ├── routes.dart # 插件路由注册表(左侧菜单与内容页共用,加插件=加一项)
│ ├── app_shell.dart # 应用外壳:左侧插件菜单 + IndexedStack 内容区
│ ├── card_printer_page.dart # 饭卡打印机插件页:主状态一眼可懂 + 指标卡 + 技术详情折叠
│ └── app_tray.dart # 后台托盘:关窗隐藏(window_manager)/托盘菜单(tray_manager)/恢复窗口/退出
├── server/
│ ├── protocol.dart # 协议 DTO(toJson 手写驼峰,对齐 C# ApiModels.cs)
│ ├── http_server.dart # shelf 服务:端口顺延、CORS、四端点
│ └── print_queue.dart # 串行任务队列 + 30 分钟清理
├── print/
│ ├── backend.dart # PrintBackend 抽象 + OS 工厂
│ ├── mock_backend.dart # --mock 模拟(打印 2.5s 完成)
│ ├── cups_backend.dart # macOS:lpoptions 发现队列 + lp 提交(参数列表传参防注入)
│ └── pavo/ # Windows:HiTi PavoApi ffi 直打
│ ├── pavo_ffi.dart # 结构体/导出绑定(PavoApiW_x64.dll,W 后缀回退兼容)
│ ├── pavo_printer.dart # 枚举/状态/色带/单卡直打/轮询/内存管理(对齐 C# PavoPrinter.cs)
│ └── pavo_backend.dart # 后端实现(全部跑在 Isolate.run,主 isolate 不阻塞)
├── image/
│ └── card_image.dart # PNG data URL → 缩放铺白底 → BGR24/Gray8(对齐 C# CardImage.cs)
├── utils/
│ └── command_runner.dart # 外部命令执行(LC_ALL=C、超时杀进程、参数列表)
bin/agent.dart # headless 入口(开发联调,行为对齐 C# 控制台助手)
tool/make_test_card.dart # 生成 1014×642 测试卡(真实打印验收用)
```
## 常用命令
```bash
flutter pub get
flutter analyze
flutter test
# headless 联调(不需要窗口/真机)
dart run bin/agent.dart --mock # mock 模式,端口 38200-38202 顺延
dart run bin/agent.dart --port 38203 # 指定端口(本机 C# 助手占用时的开发姿势)
dart run bin/agent.dart # 真实模式(macOS 走 CUPS 队列)
# 测试卡 + 真实打印
dart run tool/make_test_card.dart
# 生成 data URL 后 POST /api/card-printer/print,观察 lpoptions 的 printer-state 4→3
# 构建
flutter build macos --release
```
## 本机开发注意(血泪坑,勿再踩)
- **同机 C# 助手占端口**:测试前 `launchctl unload ~/Library/LaunchAgents/com.zax.cardprinteragent.plist`,测完 `launchctl load` 恢复;两个版本的 manifest.agent 字段可区分(card-printer-agent / desktop-plugin)
- **AMFI 卡死进程**:早前测试遗留的 CardPrinterAgent 进程 STAT=UE/UNE,kill -9 无效,占用 38201 直到重启;Flutter 版会自动落到其他候选端口,前端自适应
- **macOS 必须关 App Sandbox**(`macos/Runner/*.entitlements` 已置空):沙箱拦截 127.0.0.1 端口绑定与 lp/lpoptions 子进程访问 CUPS
- **macos 平台目录生成前提**:本机 flutter config 曾禁用 `enable-macos-desktop` 且缺 CocoaPods,均已修复(brew install cocoapods;注意 brew 装 ca-certificates 链接冲突时先 `brew unlink` 再重装)
- **windows/ 目录无法在 Mac 生成**:需在 Windows 实机上 `flutter create --platforms windows .` 补齐(Phase 3)
## 进度(2026-09-21)
- [x] Phase 1:协议服务器 + mock + 任务队列 + 端口顺延(单测全过,接口自测全过)
- [x] Phase 2:macOS CUPS 后端(真机 status `HiTi CS-200e, 2.4.2 就绪`;真实出卡一张)
- [x] Phase 3 代码:Windows PavoApi ffi 后端(待 Windows 实机验收)
- [x] Phase 4 全部:插件化 UI + 后台托盘(红点/`_close:` 直退已修,根因=模板 AppDelegate 的 `applicationShouldTerminateAfterLastWindowClosed=true`)+ 资安信品牌 + **首次引导向导**(服务自检/打印机检测与驱动指引/自启配置)+ **开机自启**(mac LaunchAgent RunAtLoad / Win HKCU Run,侧栏开关)+ **URL scheme 闭环**(单实例唤醒端口 38210:`zaxprint://` 或重复双击 → 唤醒常驻实例弹窗;Win 启动时幂等注册 HKCU scheme;前端抽屉离线时"启动打印助手"按钮)
- [x] Phase 5 mac:`tool/package_macos.sh`(构建 → ad-hoc 就地签名 → dmg,实测 22MB 产物 + 签名后冒烟通过);Windows:`windows/installer.iss`(Inno Setup,含 scheme/自启注册与卸载清理,待 Windows 机上 iscc 编译)
- [ ] Windows 侧打包与实机验收(Inno 编译 + Pavo 直打/色带/免驱动实测)
### 已知坑(本项目实测)
- SliverList 里 `Row(crossAxisAlignment: stretch)` 必须包 `IntrinsicHeight`,否则子项收到 h=Infinity 直接布局断言(test/ui_smoke_test.dart 守护)
- testWidgets 里 `agent.start()/stop()` 是真实 socket I/O,必须 `tester.runAsync` 包裹,否则 fake async zone 永久挂起;且勿用 pumpAndSettle(页面含动画组件永不 settle)
- image 包 `Image()` 默认 3 通道不透明黑(透明图须 numChannels:4);`compositeImage` 对全透明像素非 src-over 语义
- tray_manager 0.5.3:无双击事件(单击即 MouseDown);菜单须先 setContextMenu 再无参 popUpContextMenu
### Windows 实机验收清单(Phase 3 收尾)
1. `flutter create --platforms windows .` 对照生成的目录结构核对手动实例化的差异(或直接构建)
2. `flutter build windows --release` 编译过;确认 vendor 四文件拷到 exe 旁
3. `dart run bin/agent.dart`(或直接跑 App)→ status 返回真机型号/固件/色带余量
4. 提交测试卡真实出卡(tool/make_test_card.dart 生成后走 print 接口)
5. 顺带实测:不装 HiTi 官方驱动能否直打(PavoApiW_x64 疑似用户态直连 USB)
### Windows 部署注意事项(代码审查结论,待实机验证)
- **中文乱码已防**:runner 已加 `/utf-8` 编译选项(MSVC 默认按系统代码页解析 UTF-8 源码,中文窗口标题/资源会乱码);Runner.rc 用 `#pragma code_page(65001)`
- **VC++ 运行库**:PavoApiW_x64.dll 若动态链接 VC runtime,缺 vcruntime140.dll 会加载失败(表现为"打印机未连接"或 DLL 加载异常)——装 [VC++ Redistributable 2015-2022](https://aka.ms/vs/17/release/vc_redist.x64.exe) 即可
- **SmartScreen**:安装包未签名,首次运行会弹"Windows 已保护你的电脑"→ 更多信息 → 仍要运行
- **ffi 布局自检**:PavoApi 构造时 assert 四个结构体 sizeof(80/32/16/48),首跑即暴露对齐差异(debug 模式生效)
- **Inno 打包**:`iscc windows\installer.iss` 需 Inno Setup 6.x(x64 架构标记用全系兼容的 `x64`)