Files
dzc 726210edcb feat: 本地日志文件系统 + 修复双击打不开/向导完成无反应
用户实测两个问题(先装好驱动的机器):
1. 双击 App 图标不一定能打开——根因 single_instance:唤醒端口 38210 被
   残留占用(上次异常退出/TIME_WAIT)时,connect 无应答仍返回 false →
   main 直接 exit(0)。修复:端口被占但无应答判定为残留占用,照常启动
   (仅唤醒功能降级,FileLog 留痕);有应答才作为第二实例退出。
2. 向导第三步点"完成"没反应——根因 _finish 只有 finally 没有 catch:
   Autostart/UrlScheme(reg 三连)/FirstRun 任一抛异常被静默吞,Navigator.pop
   永不执行=对话框锁死。修复:每步独立 try-catch + 分步日志,成败都关闭
   向导,失败汇总 SnackBar 提示"详见运行日志"。

本地日志文件系统(用户要求:仔细记录、可发回分析、定时删除):
- lib/utils/file_log.dart:按天滚动 app-YYYYMMDD.log、单文件 5MB 轮转加
  序号、启动清理 7 天前旧文件(按文件名日期判定);全同步追加写(低频
  日志,保证异常退出前落盘);目录 mac ~/Library/Logs/zax-desktop-plugin/
  win %APPDATA%\zax-desktop-plugin\logs\(不引第三方依赖)
- AgentLog.write 挂文件输出(一处改动覆盖全部 22 个现有调用点);
  main.dart FileLog.init 提到最前,bootWarning/startError 全部落日志,
  启动横幅记录平台/exe 路径/参数
- pavo isolate 的 [pavo-diag] 诊断日志传日志路径(纯数据,isolate 安全)
  直接写文件——GUI 模式下 isolate stdout 本来就丢
- 插件页技术详情加"打开日志文件夹"按钮(Explorer/Finder 直达,用户打包
  app-*.log 发回分析)

19 个测试全过、analyze 零问题。
2026-09-22 15:09:24 +08:00

128 lines
4.6 KiB
Dart
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.
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:window_manager/window_manager.dart';
import 'agent.dart';
import 'setup/single_instance.dart';
import 'setup/url_scheme.dart';
import 'ui/app_shell.dart';
import 'ui/app_tray.dart';
import 'utils/file_log.dart';
/// 资安信插件助手(desktop-plugin):桌面插件集成软件。
/// 当前内置插件:饭卡打印机(card-printer);左侧菜单与插件页均由 lib/ui/routes.dart 驱动。
/// 关闭窗口 = 最小化到后台托盘,服务持续运行;退出入口在托盘菜单。
///
/// 健壮性铁律:runApp 之前的任何非关键步骤(托盘/scheme 注册/单实例)一律
/// try-catch 吞错并记日志——否则在目标机环境差异下抛异常会让界面永远起不来
/// (窗口由原生创建、Dart 未 runApp = 白屏窗口),开发机上无法复现。
Future<void> main(List<String> args) async {
WidgetsFlutterBinding.ensureInitialized();
// 文件日志最先初始化(此后的每一步现场都可追溯;目录不可用时自动禁用)
FileLog.init();
FileLog.write('==== 启动 ====');
FileLog.write(
'环境: ${Platform.operatingSystem} ${Platform.operatingSystemVersion} / exe: ${Platform.resolvedExecutable} / args: $args');
String? bootWarning;
try {
await windowManager.ensureInitialized();
} catch (e) {
bootWarning = '窗口管理初始化失败:$e';
FileLog.write(bootWarning);
}
// 单实例:zaxprint:// 唤起或重复双击会启动第二进程——通知常驻实例弹窗后自己退出
bool isPrimary = true;
try {
isPrimary = await SingleInstance.acquire(
onWake: () async {
FileLog.write('收到唤醒(zaxprint:// 或重复双击):显示主窗口');
await windowManager.show();
await windowManager.focus();
},
);
} catch (e) {
bootWarning = '${bootWarning == null ? "" : "$bootWarning\n"}单实例检测失败:$e';
FileLog.write(bootWarning);
}
if (!isPrimary) {
FileLog.write('已有常驻实例(唤醒通知已发),本进程退出');
exit(0);
}
final agent = Agent(AgentOptions.parse(args));
String? startError;
try {
await agent.start();
} catch (e) {
startError = '$e';
agent.log.write('服务启动失败:$startError');
}
try {
await UrlScheme.ensureRegistered(); // Windows 运行时注册 zaxprint://;mac 由 Info.plist 声明
} catch (e) {
agent.log.write('网页唤起协议注册失败(不影响其他功能):$e');
}
final appTray = AppTray(agent: agent);
try {
await appTray.start();
} catch (e) {
agent.log.write('托盘初始化失败(不影响其他功能):$e');
}
agent.log.write('界面启动(日志目录:${FileLog.dir ?? "不可用"})');
runApp(DesktopPluginApp(agent: agent, startError: startError ?? bootWarning));
}
class DesktopPluginApp extends StatelessWidget {
const DesktopPluginApp({super.key, required this.agent, this.startError});
final Agent agent;
final String? startError;
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '资安信插件助手',
debugShowCheckedModeBanner: false,
theme: _darkTheme(),
home: AppShell(agent: agent, startError: startError),
);
}
/// 深色运维风格(ui-ux-pro-max 设计系统:OLED 深底 + 运维三色语义)。
ThemeData _darkTheme() {
final base = ThemeData(brightness: Brightness.dark, useMaterial3: true);
final scheme = base.colorScheme.copyWith(
// 正常/主操作:operational green
primary: const Color(0xFF4ADE80),
onPrimary: const Color(0xFF052E16),
primaryContainer: const Color(0xFF14532D),
onPrimaryContainer: const Color(0xFFDCFCE7),
// 故障红
error: const Color(0xFFF87171),
errorContainer: const Color(0xFF7F1D1D),
onErrorContainer: const Color(0xFFFEE2E2),
// 表面层级:#0F172A 底 / #111827 卡 / #1E293B 强调
surface: const Color(0xFF0F172A),
onSurface: const Color(0xFFF8FAFC),
onSurfaceVariant: const Color(0xFFCBD5E1),
surfaceContainerLow: const Color(0xFF111827),
surfaceContainerHighest: const Color(0xFF1E293B),
outlineVariant: const Color(0xFF334155),
);
return base.copyWith(
colorScheme: scheme,
scaffoldBackgroundColor: const Color(0xFF0F172A),
cardTheme: const CardTheme(color: Color(0xFF111827)),
snackBarTheme: const SnackBarThemeData(behavior: SnackBarBehavior.floating),
dividerTheme: const DividerThemeData(color: Color(0xFF334155), thickness: 1),
);
}
}