import 'dart:convert'; import 'dart:io'; import 'package:shelf/shelf.dart'; import 'package:shelf/shelf_io.dart' as shelf_io; import 'package:shelf_router/shelf_router.dart'; import '../print/backend.dart'; import 'print_queue.dart'; import 'protocol.dart'; /// 协议 HTTP 服务:候选端口依次尝试绑定(与前端扫描约定一致:38200 起顺延两个)。 class AgentHttpServer { AgentHttpServer({ required this.queue, required this.backend, required this.mock, required this.manifestBackendName, this.agentName = 'desktop-plugin', }); final PrintQueue queue; /// mock 模式下是 MockBackend,否则为系统对应后端 final PrintBackend backend; final bool mock; /// manifest 里的 backend 字段按操作系统报(pavo/cups),与是否 mock 无关(对齐 C# 版) final String manifestBackendName; final String agentName; HttpServer? _server; int? _port; int? get port => _port; Future start({List candidatePorts = const [38200, 38201, 38202]}) async { final handler = const Pipeline().addMiddleware(_cors()).addHandler(_router.call); Object? lastError; for (final port in candidatePorts) { try { _server = await shelf_io.serve( handler, InternetAddress.loopbackIPv4, port, poweredByHeader: null, ); _port = port; return port; } on SocketException catch (e) { lastError = e; } } throw StateError('端口 ${candidatePorts.first}-${candidatePorts.last} 均被占用,启动失败:$lastError'); } Future stop() async { await _server?.close(force: true); _server = null; _port = null; } Router get _router { final router = Router() ..get('/manifest', (Request r) => _ok({ 'plugins': [ManifestPlugin().toJson()], 'agent': agentName, 'backend': manifestBackendName, 'mock': mock, })) ..get('/api/card-printer/status', (Request r) async { try { return _ok((await backend.getStatus()).toJson()); } catch (e) { return _ok(PrinterStatus.offline('状态读取失败:$e').toJson()); } }) ..post('/api/card-printer/print', (Request r) async { try { final body = await r.readAsString(); final Map json; try { json = jsonDecode(body) as Map; } catch (_) { return _err('请求体不是合法 JSON'); } final taskId = queue.submit(PrintRequest.fromJson(json)); return _ok({'taskId': taskId}); } catch (e) { return _err(PrintQueue.errorMessageOf(e)); } }) ..get('/api/card-printer/task/', (Request r, String taskId) { final task = queue.get(taskId); return task == null ? _err('任务不存在或已过期') : _ok(task.toJson()); }); return router; } /// 助手只绑回环,CORS 放开 * 安全(含 OPTIONS 预检,供管理端 HTTPS 页面跨源访问)。 Middleware _cors() { const headers = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', 'Access-Control-Max-Age': '86400', }; return (inner) => (Request req) async { if (req.method == 'OPTIONS') { return Response.ok('', headers: headers); } final res = await inner(req); return res.change(headers: headers); }; } } Response _ok(Object? data) => Response.ok( jsonEncode({'code': 0, 'msg': 'ok', 'data': data}), headers: {'content-type': 'application/json; charset=utf-8'}, ); Response _err(String msg) => Response.ok( jsonEncode({'code': 1, 'msg': msg, 'data': null}), headers: {'content-type': 'application/json; charset=utf-8'}, );