Files

216 lines
6.9 KiB
C#
Raw Permalink Normal View History

2026-04-11 22:36:47 +08:00
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
2026-04-12 15:41:48 +08:00
using Cloudprint;
using Grpc.Core;
using Grpc.Net.Client;
using Microsoft.Extensions.Options;
2026-04-11 22:36:47 +08:00
namespace cloudPrintWinAgent
{
public sealed class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
2026-04-12 15:41:48 +08:00
private readonly CloudPrintOptions _opt;
public Worker(ILogger<Worker> logger, IOptions<CloudPrintOptions> cloudPrintOptions)
2026-04-11 22:36:47 +08:00
{
_logger = logger;
2026-04-12 15:41:48 +08:00
_opt = cloudPrintOptions.Value;
ValidateOptions();
2026-04-11 22:36:47 +08:00
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("WinAgent started at: {Time}", DateTimeOffset.Now);
2026-04-12 15:41:48 +08:00
// Go 后端是明文 h2chttp://),需要打开这个开关
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)
2026-04-11 22:36:47 +08:00
{
2026-04-12 15:41:48 +08:00
_logger.LogError(ex, "Worker fatal error");
2026-04-11 22:36:47 +08:00
}
2026-04-12 15:41:48 +08:00
2026-04-11 22:36:47 +08:00
_logger.LogInformation("WinAgent stopping at: {Time}", DateTimeOffset.Now);
}
2026-04-12 15:41:48 +08:00
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 不能为空");
}
2026-04-11 22:36:47 +08:00
}
}