用户实测两个问题(先装好驱动的机器): 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 零问题。
95 lines
2.8 KiB
Dart
95 lines
2.8 KiB
Dart
import 'dart:async';
|
||
|
||
import 'print/backend.dart';
|
||
import 'print/mock_backend.dart';
|
||
import 'server/http_server.dart';
|
||
import 'server/print_queue.dart';
|
||
import 'utils/file_log.dart';
|
||
|
||
/// 启动参数:--mock 模拟打印;--port=NNN / --port NNN 指定监听端口(默认 38200-38202 顺延)。
|
||
class AgentOptions {
|
||
AgentOptions({this.mock = false, this.port});
|
||
|
||
final bool mock;
|
||
final int? port;
|
||
|
||
factory AgentOptions.parse(List<String> args) {
|
||
var mock = false;
|
||
int? port;
|
||
for (var i = 0; i < args.length; i++) {
|
||
final arg = args[i];
|
||
if (arg == '--mock') {
|
||
mock = true;
|
||
} else if (arg.startsWith('--port=')) {
|
||
port = int.tryParse(arg.substring('--port='.length));
|
||
} else if ((arg == '--port' || arg == '-p') && i + 1 < args.length) {
|
||
port = int.tryParse(args[++i]);
|
||
}
|
||
}
|
||
return AgentOptions(mock: mock, port: port);
|
||
}
|
||
}
|
||
|
||
/// 运行日志环形缓冲(GUI 展示用),同时打到 stdout。
|
||
class AgentLog {
|
||
final lines = <String>[];
|
||
final _controller = StreamController<String>.broadcast();
|
||
|
||
Stream<String> get stream => _controller.stream;
|
||
|
||
void write(String line) {
|
||
final stamped = '${DateTime.now().toIso8601String().substring(11, 19)} $line';
|
||
lines.add(stamped);
|
||
if (lines.length > 500) {
|
||
lines.removeRange(0, lines.length - 500);
|
||
}
|
||
// ignore: avoid_print
|
||
print(stamped);
|
||
FileLog.write(line); // 文件持久化(供现场排查,用户可取回分析)
|
||
_controller.add(stamped);
|
||
}
|
||
|
||
void dispose() => _controller.close();
|
||
}
|
||
|
||
/// 助手核心:组合打印后端、任务队列与 HTTP 服务(Flutter 窗口与 headless 入口共用)。
|
||
class Agent {
|
||
Agent(this.options) : log = AgentLog();
|
||
|
||
final AgentOptions options;
|
||
final AgentLog log;
|
||
|
||
late final PrintQueue queue;
|
||
late final PrintBackend backend;
|
||
late final AgentHttpServer server;
|
||
int? port;
|
||
|
||
Future<int> start() async {
|
||
final backendName = osBackendName();
|
||
backend = options.mock ? MockBackend(onLog: log.write) : createOsBackend(onLog: log.write);
|
||
queue = PrintQueue(backend, onLog: log.write);
|
||
server = AgentHttpServer(
|
||
queue: queue,
|
||
backend: backend,
|
||
mock: options.mock,
|
||
manifestBackendName: backendName,
|
||
);
|
||
final ports = options.port == null ? const [38200, 38201, 38202] : <int>[options.port!];
|
||
port = await server.start(candidatePorts: ports);
|
||
log.write('资安信插件助手已启动 http://127.0.0.1:$port'
|
||
'(后端=$backendName,mock=${options.mock},agent=desktop-plugin)');
|
||
return port!;
|
||
}
|
||
|
||
bool _stopped = false;
|
||
|
||
/// 幂等停止:托盘退出与窗口销毁可能先后各调一次。
|
||
Future<void> stop() async {
|
||
if (_stopped) return;
|
||
_stopped = true;
|
||
await server.stop();
|
||
queue.dispose();
|
||
log.dispose();
|
||
}
|
||
}
|