216 lines
6.9 KiB
C#
216 lines
6.9 KiB
C#
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 后端是明文 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 不能为空");
|
||
}
|
||
}
|
||
}
|