Files
Ayay/SHH.CameraService/Grpc/CommandReceiverWorker.cs
2026-01-15 09:31:57 +08:00

123 lines
5.1 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 Grpc.Core;
using Grpc.Net.Client;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using SHH.CameraSdk;
using SHH.Contracts;
using SHH.Contracts.Grpc; // 引用 Proto 生成的命名空间
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace SHH.CameraService
{
/// <summary>
/// gRPC 指令接收后台服务
/// 负责1. 逻辑注册 2. 维持指令长连接 3. 指令分发
/// </summary>
public class GrpcCommandReceiverWorker : BackgroundService
{
private readonly ILogger<GrpcCommandReceiverWorker> _logger;
private readonly ServiceConfig _config;
private readonly IEnumerable<ICommandHandler> _handlers; // 自动注入所有指令处理器
public GrpcCommandReceiverWorker(
ILogger<GrpcCommandReceiverWorker> logger,
ServiceConfig config,
IEnumerable<ICommandHandler> handlers)
{
_logger = logger;
_config = config;
_handlers = handlers;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// 给 SDK 和数据库留出几秒钟的加载时间
_logger.LogInformation("[gRPC Bus] 后台 Worker 准备就绪3秒后发起连接...");
await Task.Delay(3000, stoppingToken);
while (!stoppingToken.IsCancellationRequested)
{
try
{
// 1. 地址预处理 (将 127.0.0.1 强制转换为 localhost 解决 Unimplemented 问题)
var ep = _config.CommandEndpoints.First();
string targetUrl = ep.Uri.Replace("tcp://", "http://").Replace("127.0.0.1", "localhost");
using var channel = GrpcChannel.ForAddress(targetUrl);
var client = new GatewayProvider.GatewayProviderClient(channel);
// --- 第一步:发起逻辑注册 (Unary) ---
_logger.LogInformation("[gRPC Bus] 正在发起逻辑注册: {Url}", targetUrl);
var regResp = await client.RegisterInstanceAsync(new RegisterRequest
{
InstanceId = _config.AppId,
Version = "2.0.0-grpc",
ServerIp = "127.0.0.1",
StartTimeTicks = DateTime.Now.Ticks
}, cancellationToken: stoppingToken);
if (regResp.Success)
{
_logger.LogInformation("[gRPC Bus] 逻辑注册成功。正在开启长连接指令通道...");
// --- 第二步:开启物理指令流 (Server Streaming) ---
using var call = client.OpenCommandChannel(new CommandStreamRequest
{
InstanceId = _config.AppId
}, cancellationToken: stoppingToken);
// --- 第三步:阻塞式监听服务端推送 ---
// 只要服务端通过 responseStream.WriteAsync 发消息,这里就会命中
while (await call.ResponseStream.MoveNext(stoppingToken))
{
var protoMsg = call.ResponseStream.Current;
_logger.LogInformation("[gRPC Bus] 收到远程指令: {CmdCode}", protoMsg.CmdCode);
// 异步分发,不阻塞接收循环
_ = DispatchCommandAsync(protoMsg);
}
}
}
catch (OperationCanceledException) { break; }
catch (Exception ex)
{
_logger.LogError("[gRPC Bus] 链路异常5秒后重试: {Msg}", ex.Message);
await Task.Delay(5000, stoppingToken);
}
}
}
/// <summary>
/// 指令分发逻辑
/// </summary>
private async Task DispatchCommandAsync(CommandPayloadProto msg)
{
try
{
// 1. 寻找匹配的处理器 (SyncCameraHandler / RemoveCameraHandler)
var handler = _handlers.FirstOrDefault(h => h.ActionName == msg.CmdCode);
if (handler != null)
{
// 2. 将 Proto 的参数转为 JToken保持与原有处理器兼容
var jsonParams = JToken.Parse(msg.JsonParams);
await handler.ExecuteAsync(jsonParams);
_logger.LogInformation("[gRPC Bus] 指令 {CmdCode} 执行完成", msg.CmdCode);
}
else
{
_logger.LogWarning("[gRPC Bus] 未找到处理 {CmdCode} 的处理器", msg.CmdCode);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "[gRPC Bus] 指令执行失败: {CmdCode}", msg.CmdCode);
}
}
}
}