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 后端是明文 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)
|
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
|
|
|
|
}
|
|
|
|
|
|
}
|