using Ayay.SerilogLogs; using Newtonsoft.Json.Linq; using Serilog; using SHH.Contracts.Grpc; namespace SHH.CameraService; /// /// gRpc 指令分发器 /// 职责:接收从 GrpcCommandReceiverWorker 传入的 Proto 消息,解析参数并路由至具体的 Handler。 /// public class CommandDispatcher { private static ILogger _gRpcLog = Log.ForContext("SourceContext", LogModules.gRpc); private readonly Dictionary _handlers; /// /// 构造函数:通过 DI 注入所有已注册的处理器 (SyncCameraHandler, RemoveCameraHandler 等) /// public CommandDispatcher(IEnumerable handlers) { // 将处理器列表转换为字典,方便 O(1) 查询 _handlers = handlers.ToDictionary( h => h.ActionName, h => h, StringComparer.OrdinalIgnoreCase); } /// /// 执行指令分发 /// /// 从 gRpc Server Streaming 接收到的原始 Proto 指令对象 public async Task DispatchAsync(CommandPayloadProto protoMsg) { if (protoMsg == null) return; string cmdCode = protoMsg.CmdCode; // 例如 "Sync_Camera" _gRpcLog.Information($"[gRpc] 响应请求, 业务:{protoMsg.CmdCode}, 请求ID:{protoMsg.RequestId}, 业务分发."); _gRpcLog.Debug($"[gRpc] 响应请求, {protoMsg.CmdCode}, 请求ID:{protoMsg.RequestId}, 业务分发 => {protoMsg}"); try { // 1. 查找对应的处理器 if (_handlers.TryGetValue(cmdCode, out var handler)) { // 2. 参数转换:将 Proto 里的 JSON 字符串转换为原有 Handler 需要的 JToken // 这样你之前的 SyncCameraHandler 代码不需要做任何逻辑改动即可直接复用 var jsonStr = string.IsNullOrWhiteSpace(protoMsg.JsonParams) ? "{}" : protoMsg.JsonParams; var token = JToken.Parse(jsonStr); // 3. 调用具体业务执行 await handler.ExecuteAsync(token); _gRpcLog.Information($"[gRpc] 业务:{protoMsg.CmdCode}, 请求ID:{protoMsg.RequestId}, 执行成功."); } else { _gRpcLog.Warning($"[gRpc] 业务:{protoMsg.CmdCode}, 请求ID:{protoMsg.RequestId}, 未找到指令处理器."); } } catch (Exception ex) { _gRpcLog.Error($"[gRpc] 业务:{protoMsg.CmdCode}, 请求ID:{protoMsg.RequestId}, 执行指令处理异常: {ex.Message}."); } // 注意:关于 ACK (require_ack) // 在 NetMQ 时代需要手动回发结果,在 gRpc Server Streaming 模式下, // 建议通过 Unary RPC (例如另设一个 ReportCommandResult 方法) 异步上报执行结果。 } }