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

8.6 KiB
Raw Permalink Blame History

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 测试卡(真实打印验收用)

常用命令

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)

  • Phase 1:协议服务器 + mock + 任务队列 + 端口顺延(单测全过,接口自测全过)
  • Phase 2:macOS CUPS 后端(真机 status HiTi CS-200e, 2.4.2 就绪;真实出卡一张)
  • Phase 3 代码:Windows PavoApi ffi 后端(待 Windows 实机验收)
  • Phase 4 全部:插件化 UI + 后台托盘(红点/_close: 直退已修,根因=模板 AppDelegate 的 applicationShouldTerminateAfterLastWindowClosed=true)+ 资安信品牌 + 首次引导向导(服务自检/打印机检测与驱动指引/自启配置)+ 开机自启(mac LaunchAgent RunAtLoad / Win HKCU Run,侧栏开关)+ URL scheme 闭环(单实例唤醒端口 38210:zaxprint:// 或重复双击 → 唤醒常驻实例弹窗;Win 启动时幂等注册 HKCU scheme;前端抽屉离线时"启动打印助手"按钮)
  • 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 即可
  • SmartScreen:安装包未签名,首次运行会弹"Windows 已保护你的电脑"→ 更多信息 → 仍要运行
  • ffi 布局自检:PavoApi 构造时 assert 四个结构体 sizeof(80/32/16/48),首跑即暴露对齐差异(debug 模式生效)
  • Inno 打包:iscc windows\installer.iss 需 Inno Setup 6.x(x64 架构标记用全系兼容的 x64)