2026-01-16 07:23:56 +08:00
|
|
|
|
using Ayay.SerilogLogs;
|
|
|
|
|
|
using Grpc.Core;
|
2026-01-15 11:04:38 +08:00
|
|
|
|
using Grpc.Net.Client;
|
|
|
|
|
|
using Microsoft.Extensions.Hosting;
|
2026-01-16 07:23:56 +08:00
|
|
|
|
using Serilog;
|
2026-01-15 11:04:38 +08:00
|
|
|
|
using SHH.CameraSdk;
|
|
|
|
|
|
using SHH.Contracts.Grpc; // 引用 Proto 生成的命名空间
|
|
|
|
|
|
|
|
|
|
|
|
namespace SHH.CameraService
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// gRPC 指令接收后台服务
|
|
|
|
|
|
/// 职责:
|
|
|
|
|
|
/// 1. 维护与 AiVideo 的 gRPC 长连接。
|
|
|
|
|
|
/// 2. 完成节点逻辑注册。
|
|
|
|
|
|
/// 3. 监听 Server Streaming 指令流并移交给 Dispatcher。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class GatewayService : BackgroundService
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly ServiceConfig _config;
|
|
|
|
|
|
private readonly CommandDispatcher _dispatcher;
|
|
|
|
|
|
|
|
|
|
|
|
public GatewayService(
|
|
|
|
|
|
ServiceConfig config,
|
|
|
|
|
|
CommandDispatcher dispatcher)
|
|
|
|
|
|
{
|
|
|
|
|
|
_config = config;
|
|
|
|
|
|
_dispatcher = dispatcher;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
|
|
|
|
{
|
2026-01-16 07:23:56 +08:00
|
|
|
|
var gRpcLog = Log.ForContext("SourceContext", LogModules.gRpc);
|
|
|
|
|
|
|
2026-01-15 11:04:38 +08:00
|
|
|
|
// 预留系统启动缓冲时间,确保数据库和 SDK 已就绪
|
2026-01-16 07:23:56 +08:00
|
|
|
|
gRpcLog.Information("[gRPC] 指令接收服务启动,等待环境预热...");
|
2026-01-15 11:04:38 +08:00
|
|
|
|
await Task.Delay(3000, stoppingToken);
|
|
|
|
|
|
|
|
|
|
|
|
while (!stoppingToken.IsCancellationRequested)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 1. 地址适配:将 tcp 转换为 http,并将 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) ---
|
2026-01-16 07:23:56 +08:00
|
|
|
|
gRpcLog.Information("[gRPC] 正在发起逻辑注册: {Url}", targetUrl);
|
2026-01-15 11:04:38 +08:00
|
|
|
|
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)
|
|
|
|
|
|
{
|
2026-01-16 07:23:56 +08:00
|
|
|
|
gRpcLog.Information("[gRPC] 注册成功, 正在建立双向指令通道...");
|
2026-01-15 11:04:38 +08:00
|
|
|
|
|
|
|
|
|
|
// --- 第二步:开启 Server Streaming 指令流 ---
|
|
|
|
|
|
using var call = client.OpenCommandChannel(new CommandStreamRequest
|
|
|
|
|
|
{
|
|
|
|
|
|
InstanceId = _config.AppId
|
|
|
|
|
|
}, cancellationToken: stoppingToken);
|
|
|
|
|
|
|
|
|
|
|
|
// --- 第三步:循环读取服务端推送的指令 ---
|
|
|
|
|
|
// 只要服务端流未断开,此处会一直阻塞等待新消息
|
|
|
|
|
|
while (await call.ResponseStream.MoveNext(stoppingToken))
|
|
|
|
|
|
{
|
|
|
|
|
|
var protoMsg = call.ResponseStream.Current;
|
|
|
|
|
|
|
|
|
|
|
|
// 核心变更:不再直接处理业务,而是通过分发器进行路由
|
|
|
|
|
|
// 使用 _ = 异步处理,避免某个 Handler 执行过慢导致指令流阻塞
|
|
|
|
|
|
_ = _dispatcher.DispatchAsync(protoMsg);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (OperationCanceledException)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 响应系统正常退出信号
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (RpcException ex)
|
|
|
|
|
|
{
|
2026-01-16 07:23:56 +08:00
|
|
|
|
gRpcLog.Debug("[gRPC] RPC 异常 (Status: {Code}): {Msg}", ex.StatusCode, ex.Message);
|
|
|
|
|
|
|
2026-01-15 11:04:38 +08:00
|
|
|
|
// 链路异常,进入重连等待阶段
|
|
|
|
|
|
await Task.Delay(5000, stoppingToken);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2026-01-16 07:23:56 +08:00
|
|
|
|
gRpcLog.Debug("[gRPC] 非预期链路异常: {Msg},5秒后尝试重连", ex.Message);
|
2026-01-15 11:04:38 +08:00
|
|
|
|
await Task.Delay(5000, stoppingToken);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|