Files
windowsService/Worker.cs
2026-04-12 15:41:48 +08:00

216 lines
6.9 KiB
C#
Raw 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.
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<Worker> _logger;
private readonly CloudPrintOptions _opt;
public Worker(ILogger<Worker> logger, IOptions<CloudPrintOptions> cloudPrintOptions)
{
_logger = logger;
_opt = cloudPrintOptions.Value;
ValidateOptions();
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("WinAgent started at: {Time}", DateTimeOffset.Now);
// 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)
{
_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 不能为空");
}
}
}