using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Cloudprint; using Grpc.Core; using Grpc.Net.Client; using Microsoft.Extensions.Options; namespace cloudPrintWinAgent { public sealed class Worker : BackgroundService { private readonly ILogger _logger; private readonly CloudPrintOptions _opt; public Worker(ILogger logger, IOptions cloudPrintOptions) { _logger = logger; _opt = cloudPrintOptions.Value; ValidateOptions(); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("WinAgent started at: {Time}", DateTimeOffset.Now); // Go 后端是明文 h2c(http://),需要打开这个开关 AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); using var channel = GrpcChannel.ForAddress(_opt.ServerAddress); var client = new PrintService.PrintServiceClient(channel); try { var (printerId, token) = await RegisterPrinterAsync(client, stoppingToken); var heartbeatTask = HeartbeatLoopAsync(client, printerId, token, stoppingToken); var subscribeTask = SubscribeJobsLoopAsync(client, printerId, token, stoppingToken); await Task.WhenAll(heartbeatTask, subscribeTask); } catch (OperationCanceledException) { throw; } catch (Exception ex) { _logger.LogError(ex, "Worker fatal error"); } _logger.LogInformation("WinAgent stopping at: {Time}", DateTimeOffset.Now); } private async Task<(int printerId, string token)> RegisterPrinterAsync( PrintService.PrintServiceClient client, CancellationToken ct) { var resp = await client.RegisterPrinterAsync(new RegisterPrinterRequest { Name = _opt.PrinterName, DisplayName = _opt.PrinterDisplayName, ApiKey = _opt.ApiKey }, cancellationToken: ct); _logger.LogInformation("Registered printer. PrinterId={PrinterId}", resp.PrinterId); return (resp.PrinterId, resp.Token); } // 0: 空闲 1: 打印中 2: 离线 private async Task HeartbeatLoopAsync( PrintService.PrintServiceClient client, int printerId, string token, CancellationToken ct) { while (!ct.IsCancellationRequested) { try { await client.PrinterHeartbeatAsync(new PrinterHeartbeatRequest { PrinterId = printerId, Status = 0, Token = token }, cancellationToken: ct); _logger.LogInformation("Heartbeat ok: {Time}", DateTimeOffset.Now); } catch (RpcException ex) { _logger.LogWarning(ex, "Heartbeat rpc failed"); } await Task.Delay(TimeSpan.FromSeconds(10), ct); } } private async Task SubscribeJobsLoopAsync( PrintService.PrintServiceClient client, int printerId, string token, CancellationToken ct) { while (!ct.IsCancellationRequested) { try { using var call = client.SubscribeJobs(new SubscribeJobsRequest { PrinterId = printerId, Token = token }, cancellationToken: ct); await foreach (var job in call.ResponseStream.ReadAllAsync(ct)) { await HandleJobAsync(client, job, ct); } } catch (OperationCanceledException) { throw; } catch (RpcException ex) { _logger.LogWarning(ex, "Subscribe stream disconnected, retry in 3s"); await Task.Delay(TimeSpan.FromSeconds(3), ct); } catch (Exception ex) { _logger.LogError(ex, "Subscribe loop error, retry in 3s"); await Task.Delay(TimeSpan.FromSeconds(3), ct); } } } private async Task HandleJobAsync( PrintService.PrintServiceClient client, PrintJob job, CancellationToken ct) { _logger.LogInformation("Received job: JobNo={JobNo}, FilePath={FilePath}", job.JobNo, job.OrderItem?.File?.FilePath); try { await ReportStatusAsync(client, job.JobNo, "printing", 10, "", ct); // 模拟打印,后续替换成真实 WinAPI 打印 await Task.Delay(TimeSpan.FromSeconds(2), ct); await ReportStatusAsync(client, job.JobNo, "completed", 100, "", ct); _logger.LogInformation("Job completed: {JobNo}", job.JobNo); } catch (OperationCanceledException) { // 服务停止/任务取消 throw; } catch (Exception ex) { _logger.LogError(ex, "Job failed: {JobNo}", job.JobNo); await ReportStatusAsync(client, job.JobNo, "failed", 0, ex.Message, ct); } } private async Task ReportStatusAsync( PrintService.PrintServiceClient client, string jobNo, string status, int progress, string errorMsg, CancellationToken ct) { await client.ReportJobStatusAsync(new ReportJobStatusRequest { JobNo = jobNo, Status = status, Progress = progress, ErrorMsg = errorMsg ?? string.Empty }, cancellationToken: ct); } private void ValidateOptions() { if (string.IsNullOrWhiteSpace(_opt.ServerAddress)) throw new InvalidOperationException("CloudPrint:ServerAddress 不能为空"); if (string.IsNullOrWhiteSpace(_opt.PrinterName)) throw new InvalidOperationException("CloudPrint:PrinterName 不能为空"); if (string.IsNullOrWhiteSpace(_opt.PrinterDisplayName)) throw new InvalidOperationException("CloudPrint:PrinterDisplayName 不能为空"); if (string.IsNullOrWhiteSpace(_opt.ApiKey)) throw new InvalidOperationException("CloudPrint:ApiKey 不能为空"); } } }