阶段性批量提交
This commit is contained in:
26
SHH.CameraSdk/Configs/NetworkMode.cs
Normal file
26
SHH.CameraSdk/Configs/NetworkMode.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// 网络连接模式
|
||||
/// </summary>
|
||||
public enum NetworkMode
|
||||
{
|
||||
/// <summary>
|
||||
/// [模式0] 被动模式 (Server)
|
||||
/// <para>只监听本地端口 (Bind),等待别人来连。</para>
|
||||
/// </summary>
|
||||
Passive = 0,
|
||||
|
||||
/// <summary>
|
||||
/// [模式1] 主动模式 (Client)
|
||||
/// <para>只主动连接远程目标 (Connect),不监听本地。</para>
|
||||
/// </summary>
|
||||
Active = 1,
|
||||
|
||||
/// <summary>
|
||||
/// [模式2] 混合模式 (Both)
|
||||
/// <para>既监听本地端口,又主动连接远程目标。</para>
|
||||
/// <para>场景:本机有客户端需要看视频,同时需往云端服务器发视频。</para>
|
||||
/// </summary>
|
||||
Hybrid = 2
|
||||
}
|
||||
227
SHH.CameraSdk/Configs/ServiceConfig.cs
Normal file
227
SHH.CameraSdk/Configs/ServiceConfig.cs
Normal file
@@ -0,0 +1,227 @@
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// 全局服务配置模型 (V3 最终版)
|
||||
/// <para>负责解析命令行参数,构建网络拓扑和身份标识</para>
|
||||
/// </summary>
|
||||
public class ServiceConfig
|
||||
{
|
||||
// ==========================================
|
||||
// 1. 身份与进程属性
|
||||
// ==========================================
|
||||
|
||||
/// <summary>
|
||||
/// 父进程 PID (用于哨兵守护,--pid)
|
||||
/// </summary>
|
||||
public int ParentPid { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 应用完整标识 (例如 "CameraApp_01", --appid)
|
||||
/// </summary>
|
||||
public string AppId { get; private set; } = "Unknown_01";
|
||||
|
||||
/// <summary>
|
||||
/// 【核心】从 AppId 自动提取的数字编号
|
||||
/// <para>规则:取最后一个下划线后的数字</para>
|
||||
/// <para>示例:"CameraApp_05" -> 5</para>
|
||||
/// </summary>
|
||||
public int NumericId { get; private set; } = 1;
|
||||
|
||||
// ==========================================
|
||||
// 2. 网络连接属性 (分流)
|
||||
// ==========================================
|
||||
|
||||
/// <summary>
|
||||
/// 视频流目标地址列表 (对应 & 符号左侧)
|
||||
/// <para>ZeroMQBridgeWorker 使用此列表</para>
|
||||
/// </summary>
|
||||
public List<string> VideoEndpoints { get; private set; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// 指令控制目标地址列表 (对应 & 符号右侧)
|
||||
/// <para>CommandClientWorker 使用此列表</para>
|
||||
/// </summary>
|
||||
public List<string> CommandEndpoints { get; private set; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// WebAPI 基础端口 (--ports 的第一个值)
|
||||
/// </summary>
|
||||
public int BasePort { get; private set; } = 5000;
|
||||
|
||||
/// <summary>
|
||||
/// 端口扫描范围 (--ports 的第二个值)
|
||||
/// </summary>
|
||||
public int MaxPortRange { get; private set; } = 100;
|
||||
|
||||
/// <summary>
|
||||
/// 网络模式 (--mode)
|
||||
/// </summary>
|
||||
public NetworkMode Mode { get; private set; } = NetworkMode.Passive;
|
||||
|
||||
// ==========================================
|
||||
// 3. 辅助属性
|
||||
// ==========================================
|
||||
|
||||
/// <summary>
|
||||
/// 是否需要执行 Connect 操作
|
||||
/// </summary>
|
||||
public bool ShouldConnect => Mode == NetworkMode.Active || Mode == NetworkMode.Hybrid;
|
||||
|
||||
// ==========================================
|
||||
// 4. 解析入口 (Factory Method)
|
||||
// ==========================================
|
||||
|
||||
public static ServiceConfig BuildFromArgs(string[] args)
|
||||
{
|
||||
var config = new ServiceConfig();
|
||||
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
// 1. 预处理 Key
|
||||
var key = args[i].ToLower().Trim();
|
||||
|
||||
// 2. 预取 Value (如果存在且不是下一个 flag)
|
||||
var value = (i + 1 < args.Length) ? args[i + 1] : string.Empty;
|
||||
|
||||
// 简单判断:如果 value 以 -- 开头,说明当前 key 是开关,或者参数值缺失
|
||||
if (value.StartsWith("--")) value = string.Empty;
|
||||
|
||||
bool consumed = false; // 标记是否消耗了下一个参数
|
||||
|
||||
// 3. 匹配参数
|
||||
switch (key)
|
||||
{
|
||||
case "--pid":
|
||||
if (int.TryParse(value, out int pid)) config.ParentPid = pid;
|
||||
consumed = true;
|
||||
break;
|
||||
|
||||
case "--appid":
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
config.AppId = value;
|
||||
// ★★★ 立即解析数字编号 ★★★
|
||||
config.NumericId = ParseIdFromAppId(value);
|
||||
}
|
||||
consumed = true;
|
||||
break;
|
||||
|
||||
case "--uris":
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
// ★★★ 解析复杂 URI 字符串 ★★★
|
||||
ParseUris(config, value);
|
||||
}
|
||||
consumed = true;
|
||||
break;
|
||||
|
||||
case "--mode":
|
||||
if (int.TryParse(value, out int m) && Enum.IsDefined(typeof(NetworkMode), m))
|
||||
{
|
||||
config.Mode = (NetworkMode)m;
|
||||
}
|
||||
consumed = true;
|
||||
break;
|
||||
|
||||
case "--ports":
|
||||
// 格式: "BasePort,Range" -> "6003,100"
|
||||
if (!string.IsNullOrWhiteSpace(value) && value.Contains(","))
|
||||
{
|
||||
var parts = value.Split(',');
|
||||
if (parts.Length >= 1)
|
||||
{
|
||||
if (int.TryParse(parts[0], out int baseP)) config.BasePort = baseP;
|
||||
}
|
||||
if (parts.Length >= 2)
|
||||
{
|
||||
if (int.TryParse(parts[1], out int range)) config.MaxPortRange = range;
|
||||
}
|
||||
}
|
||||
consumed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// 4. 如果消耗了 Value,跳过下一个索引
|
||||
if (consumed) i++;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 5. 核心解析算法实现
|
||||
// ==========================================
|
||||
|
||||
/// <summary>
|
||||
/// 算法:提取下划线后的数字
|
||||
/// </summary>
|
||||
private static int ParseIdFromAppId(string appId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(appId)) return 1;
|
||||
|
||||
// 查找最后一个下划线
|
||||
int lastIdx = appId.LastIndexOf('_');
|
||||
|
||||
// 确保下划线存在,且后面还有字符
|
||||
if (lastIdx >= 0 && lastIdx < appId.Length - 1)
|
||||
{
|
||||
string numPart = appId.Substring(lastIdx + 1);
|
||||
if (int.TryParse(numPart, out int id))
|
||||
{
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
// 解析失败默认返回 1
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 算法:解析 URI 列表并分流
|
||||
/// <para>格式: IP,VideoPort&CommandPort</para>
|
||||
/// <para>空缺处理: "&6001" (仅指令), "6002&" (仅视频)</para>
|
||||
/// </summary>
|
||||
private static void ParseUris(ServiceConfig config, string rawValue)
|
||||
{
|
||||
// 1. 按分号拆分不同主机配置
|
||||
// "127.0.0.1,6002&6001; 192.168.1.5,&6001"
|
||||
var groups = rawValue.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (var group in groups)
|
||||
{
|
||||
// 2. 按逗号拆分 IP 和 端口段
|
||||
var hostParts = group.Split(',');
|
||||
if (hostParts.Length < 2) continue; // 格式非法
|
||||
|
||||
string ip = hostParts[0].Trim();
|
||||
string portSection = hostParts[1].Trim(); // "6002&6001"
|
||||
|
||||
// 3. 按 & 拆分端口 (注意:不要 RemoveEmptyEntries,位置很重要)
|
||||
var ports = portSection.Split('&');
|
||||
|
||||
// --- 索引 0: 视频端口 ---
|
||||
if (ports.Length > 0)
|
||||
{
|
||||
string p = ports[0].Trim();
|
||||
if (!string.IsNullOrWhiteSpace(p) && int.TryParse(p, out int port))
|
||||
{
|
||||
string uri = $"tcp://{ip}:{port}";
|
||||
if (!config.VideoEndpoints.Contains(uri))
|
||||
config.VideoEndpoints.Add(uri);
|
||||
}
|
||||
}
|
||||
|
||||
// --- 索引 1: 指令端口 ---
|
||||
if (ports.Length > 1)
|
||||
{
|
||||
string p = ports[1].Trim();
|
||||
if (!string.IsNullOrWhiteSpace(p) && int.TryParse(p, out int port))
|
||||
{
|
||||
string uri = $"tcp://{ip}:{port}";
|
||||
if (!config.CommandEndpoints.Contains(uri))
|
||||
config.CommandEndpoints.Add(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
82
SHH.CameraSdk/Core/ServiceExtensions.cs
Normal file
82
SHH.CameraSdk/Core/ServiceExtensions.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace SHH.CameraSdk
|
||||
{
|
||||
public static class ServiceExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 注入 CameraSdk 的核心服务
|
||||
/// <para>包含:内存缓存、配置管理、图像流水线、存储服务、相机管理、窗口管理等</para>
|
||||
/// </summary>
|
||||
/// <param name="services">DI 容器</param>
|
||||
/// <param name="processId">进程ID (用于确定存储路径)</param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddCameraSdk(this IServiceCollection services, int processId)
|
||||
{
|
||||
// =============================================================
|
||||
// 1. 基础组件注册 (修复你之前的报错)
|
||||
// =============================================================
|
||||
services.AddMemoryCache(); // ★ 核心修复:添加内存缓存
|
||||
|
||||
// 注册配置管理器(指挥部)
|
||||
services.AddSingleton<ProcessingConfigManager>();
|
||||
|
||||
// =============================================================
|
||||
// 2. 图像处理流水线编排 (Pipeline)
|
||||
// =============================================================
|
||||
// 这里我们利用 Factory 模式在注册时完成链条组装,保持了你原有的逻辑
|
||||
services.AddSingleton<ImageScaleCluster>(sp =>
|
||||
{
|
||||
var configMgr = sp.GetRequiredService<ProcessingConfigManager>();
|
||||
|
||||
// 手动创建实例
|
||||
var scale = new ImageScaleCluster(4, configMgr);
|
||||
var enhance = new ImageEnhanceCluster(4, configMgr);
|
||||
|
||||
// ★ 编排流水线:缩放 -> 增亮
|
||||
scale.SetNext(enhance);
|
||||
|
||||
// ★ 全局路由挂载 (兼容旧驱动层)
|
||||
GlobalPipelineRouter.SetProcessor(scale);
|
||||
|
||||
return scale;
|
||||
});
|
||||
|
||||
// 注册 EnhanceCluster,以防 Controller 单独请求它
|
||||
// 注意:这里我们通过从 Scale 中获取 Next 来保证是同一个实例链条
|
||||
services.AddSingleton<ImageEnhanceCluster>(sp =>
|
||||
{
|
||||
var scale = sp.GetRequiredService<ImageScaleCluster>();
|
||||
// 这里假设链条没变,或者你可以重新 new 一个,但为了保持引用一致性,
|
||||
// 建议尽量通过主入口访问,或者在这里重新创建独立的(取决于业务需求)。
|
||||
// 按照你之前的逻辑,这里为了简单,我们重新注册一个新的或沿用上一个逻辑。
|
||||
// *最佳实践*:如果 enhancing 是依附于 scaling 的,通常只注册 Head。
|
||||
// 但为了兼容你原代码的 DI 注册:
|
||||
return new ImageEnhanceCluster(4, sp.GetRequiredService<ProcessingConfigManager>());
|
||||
});
|
||||
|
||||
// =============================================================
|
||||
// 3. 核心业务服务
|
||||
// =============================================================
|
||||
|
||||
// 文件存储服务 (依赖 processId)
|
||||
services.AddSingleton<IStorageService>(sp => new FileStorageService(processId));
|
||||
|
||||
// 核心设备管理器 (自动注入 IStorageService)
|
||||
services.AddSingleton<CameraManager>();
|
||||
|
||||
// 动态窗口管理器 (自动注入 CameraManager)
|
||||
services.AddSingleton<DisplayWindowManager>();
|
||||
|
||||
// 网络哨兵 (建议注册为单例,方便后续获取状态)
|
||||
services.AddSingleton<ConnectivitySentinel>();
|
||||
|
||||
// =============================================================
|
||||
// 4. Web 过滤器
|
||||
// =============================================================
|
||||
services.AddScoped<UserActionFilter>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using OpenCvSharp;
|
||||
using SHH.CameraSdk.HikFeatures;
|
||||
using System;
|
||||
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
@@ -380,11 +379,9 @@ public class HikVideoSource : BaseVideoSource,
|
||||
smartFrame.SubscriberIds.AddRange(decision.TargetAppIds);
|
||||
|
||||
// =========================================================================
|
||||
// 【新增】插入这一行!
|
||||
// 此时 smartFrame.InternalMat 已经有了图像数据
|
||||
// 我们把它交给全局分发器,触发 ZeroMQ 广播
|
||||
// =========================================================================
|
||||
GlobalStreamDispatcher.Dispatch(Id, smartFrame);
|
||||
// 【修正】删除这里的 GlobalStreamDispatcher.Dispatch!
|
||||
// 严禁在这里分发,因为这时的图是“生的”,还没经过 Pipeline 处理。
|
||||
// =========================================================================GlobalStreamDispatcher.Dispatch(Id, smartFrame);
|
||||
|
||||
// 4. [分发] 将决策结果传递给处理中心
|
||||
// decision.TargetAppIds 包含了 "谁需要这一帧" 的信息
|
||||
|
||||
@@ -1,215 +1,116 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// A 方案:标准控制台结构 (框架搭建版:支持动态端口与依赖注入)
|
||||
/// </summary>
|
||||
public class Program
|
||||
{
|
||||
[STAThread]
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
// ==============================================================================
|
||||
// 1. 身份识别与端口计算
|
||||
// ==============================================================================
|
||||
|
||||
// 默认 1 号进程
|
||||
int processId = 1;
|
||||
|
||||
// 如果命令行传了参数 (例如: dotnet run 2),则覆盖为 2 号进程
|
||||
if (args.Length > 0 && int.TryParse(args[0], out int pid))
|
||||
{
|
||||
processId = pid;
|
||||
}
|
||||
|
||||
// 端口计算公式:5000 + (ID - 1)
|
||||
// ID=1 -> 5000
|
||||
// ID=2 -> 5001
|
||||
if (args.Length > 0 && int.TryParse(args[0], out int pid)) processId = pid;
|
||||
int port = 5000 + (processId - 1);
|
||||
|
||||
Console.Title = $"SHH Gateway - Instance #{processId} (Port: {port})";
|
||||
Console.WriteLine($"[System] 正在初始化实例 #{processId}...");
|
||||
|
||||
// ==============================================================================
|
||||
// 2. 基础设施初始化
|
||||
// ==============================================================================
|
||||
// 2. 硬件预热 (静态方法保留)
|
||||
InitHardwareEnv();
|
||||
|
||||
// B. 【核心】创建独立的文件存储服务 (此时只建立目录,不进行具体读写)
|
||||
IStorageService storageService = new FileStorageService(processId);
|
||||
// 3. 创建 WebHost 并加载 SDK
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// 核心设备管理器
|
||||
// 注意:暂时保持无参构造,后续我们在改造 CameraManager 时再注入 storageService
|
||||
using var cameraManager = new CameraManager(storageService);
|
||||
// ★★★★★ 核心变化:调用扩展方法加载 SDK ★★★★★
|
||||
// 这行代码把 MemoryCache、CameraManager、流水线全部配好了
|
||||
builder.Services.AddCameraSdk(processId);
|
||||
|
||||
// 动态窗口管理器
|
||||
var displayManager = new DisplayWindowManager(cameraManager);
|
||||
// 4. 配置 Web 相关的服务 (Swagger, Controllers, CORS)
|
||||
ConfigureWebServices(builder, processId);
|
||||
|
||||
// ==============================================================================
|
||||
// 3. 启动 Web 监控与诊断服务 (注入服务与端口)
|
||||
// ==============================================================================
|
||||
var app = await StartWebMonitoring(cameraManager, displayManager, storageService, port);
|
||||
var app = builder.Build();
|
||||
|
||||
// 启动网络哨兵
|
||||
var sentinel = new ConnectivitySentinel(cameraManager);
|
||||
// 5. 配置中间件管道
|
||||
ConfigureMiddleware(app, processId);
|
||||
|
||||
// ==============================================================================
|
||||
// 4. 业务编排
|
||||
// ==============================================================================
|
||||
|
||||
// 【关键修复 1】先 StartAsync,让它先从文件把 999 号设备读进内存
|
||||
await cameraManager.StartAsync();
|
||||
// 6. 启动业务逻辑
|
||||
await StartBusinessLogic(app);
|
||||
|
||||
// 【关键修复 2】文件加载完后,再决定要不要加默认设备
|
||||
await ConfigureBusinessLogic(cameraManager);
|
||||
// 7. 启动 Web 监听
|
||||
_ = app.RunAsync($"http://0.0.0.0:{port}");
|
||||
Console.WriteLine($"[System] 网关 #{processId} 就绪。地址: http://localhost:{port}");
|
||||
|
||||
// ==============================================================================
|
||||
// 5. 启动引擎与交互
|
||||
// ==============================================================================
|
||||
Console.WriteLine("\n[系统] 正在启动全局管理引擎...");
|
||||
|
||||
Console.WriteLine($">> 系统就绪。Web 管理地址: http://localhost:{port}");
|
||||
Console.WriteLine($">> 数据存储路径: App_Data/Process_{processId}/");
|
||||
// 8. 阻塞驻留
|
||||
Console.WriteLine(">> 按 'S' 键退出...");
|
||||
while (Console.ReadKey(true).Key != ConsoleKey.S) { Thread.Sleep(100); }
|
||||
|
||||
// 阻塞主线程
|
||||
while (Console.ReadKey(true).Key != ConsoleKey.S)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
Console.WriteLine("[系统] 正在停机...");
|
||||
Console.WriteLine("[System] 正在停机...");
|
||||
await app.StopAsync();
|
||||
}
|
||||
|
||||
// ==============================================================================
|
||||
// Static Methods
|
||||
// ==============================================================================
|
||||
// --- 下面是拆分出来的私有辅助方法,让 Main 看起来更清晰 ---
|
||||
|
||||
static void InitHardwareEnv()
|
||||
static void ConfigureWebServices(WebApplicationBuilder builder, int processId)
|
||||
{
|
||||
Console.WriteLine("=== 工业级视频 SDK 架构测试 (V3.5 框架版) ===");
|
||||
Console.WriteLine("[硬件] 海康驱动预热中...");
|
||||
HikNativeMethods.NET_DVR_Init();
|
||||
HikSdkManager.ForceWarmUp();
|
||||
Console.WriteLine("[硬件] 预热完成。");
|
||||
}
|
||||
|
||||
static async Task<WebApplication> StartWebMonitoring(
|
||||
CameraManager manager,
|
||||
DisplayWindowManager displayMgr,
|
||||
IStorageService storage, // 接收存储服务实例
|
||||
int port) // 接收动态端口
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder();
|
||||
|
||||
// 1. 注册配置管理器(指挥部)
|
||||
var configManager = new ProcessingConfigManager();
|
||||
builder.Services.AddSingleton(configManager);
|
||||
|
||||
// 2. 初始化预处理流水线环节
|
||||
// 建议:此处直接手动创建实例,以便精确控制链条顺序
|
||||
var scaleService = new ImageScaleCluster(4, configManager); // 环节一
|
||||
var enhanceService = new ImageEnhanceCluster(4, configManager); // 环节二
|
||||
|
||||
// 3. 编排流水线:缩放 -> 增亮 -> 终点(GlobalProcessingCenter)
|
||||
scaleService.SetNext(enhanceService);
|
||||
|
||||
// 4. 将流水线入口挂载到全局路由(驱动层改道)
|
||||
GlobalPipelineRouter.SetProcessor(scaleService);
|
||||
|
||||
// 5. 【修复点】将具体实例注册到 DI 容器
|
||||
// 这样 Controller 可以通过构造函数拿到具体的实例进行动态管理
|
||||
builder.Services.AddSingleton(scaleService);
|
||||
builder.Services.AddSingleton(enhanceService);
|
||||
|
||||
// 6. 配置 CORS
|
||||
// CORS
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("AllowAll", policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod();
|
||||
});
|
||||
options.AddPolicy("AllowAll", p => p.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());
|
||||
});
|
||||
|
||||
// 7. 依赖注入注册
|
||||
builder.Services.AddSingleton<IStorageService>(storage);
|
||||
builder.Services.AddSingleton(manager);
|
||||
builder.Services.AddSingleton(displayMgr);
|
||||
|
||||
//// 2. 日志降噪
|
||||
//builder.Logging.SetMinimumLevel(LogLevel.Warning);
|
||||
//builder.Logging.AddFilter("Microsoft.AspNetCore.Hosting.Diagnostics", LogLevel.Warning);
|
||||
|
||||
|
||||
// 显式注册过滤器 (这是防止 500 错误的关键)
|
||||
builder.Services.AddScoped<UserActionFilter>();
|
||||
|
||||
// Controllers & Filters
|
||||
builder.Services.AddControllers(options =>
|
||||
{
|
||||
// 注册全局操作日志过滤器
|
||||
options.Filters.Add<UserActionFilter>();
|
||||
});
|
||||
|
||||
// Swagger
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen(c =>
|
||||
{
|
||||
c.SwaggerDoc("v1", new OpenApiInfo { Title = $"SHH Gateway #{processIdFromPort(port)}", Version = "v1" });
|
||||
c.SwaggerDoc("v1", new OpenApiInfo { Title = $"SHH Gateway #{processId}", Version = "v1" });
|
||||
});
|
||||
|
||||
var webApp = builder.Build();
|
||||
|
||||
// 4. 配置中间件
|
||||
webApp.UseSwagger();
|
||||
webApp.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Diagnostics V1"));
|
||||
webApp.UseCors("AllowAll");
|
||||
webApp.MapControllers();
|
||||
|
||||
// 5. 启动监听 (使用动态端口)
|
||||
_ = webApp.RunAsync($"http://0.0.0.0:{port}");
|
||||
Console.WriteLine($"[Web] 监控API已启动: http://localhost:{port}");
|
||||
|
||||
return webApp;
|
||||
}
|
||||
|
||||
// 辅助方法:从端口反推 ID,仅用于 Swagger 标题显示
|
||||
static int processIdFromPort(int port) => port - 5000 + 1;
|
||||
static void ConfigureMiddleware(WebApplication app, int processId)
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", $"Gateway {processId}"));
|
||||
app.UseCors("AllowAll");
|
||||
app.MapControllers();
|
||||
}
|
||||
|
||||
static async Task ConfigureBusinessLogic(CameraManager manager)
|
||||
static async Task StartBusinessLogic(WebApplication app)
|
||||
{
|
||||
// 从 DI 容器中获取已经注册好的服务
|
||||
var cameraManager = app.Services.GetRequiredService<CameraManager>();
|
||||
|
||||
// 必须显式获取一次 Sentinel 确保它被实例化并开始工作
|
||||
var sentinel = app.Services.GetRequiredService<ConnectivitySentinel>();
|
||||
|
||||
// 启动相机的加载逻辑
|
||||
await cameraManager.StartAsync();
|
||||
|
||||
// 添加测试设备 (原有逻辑)
|
||||
await AddTestDevices(cameraManager);
|
||||
}
|
||||
|
||||
static void InitHardwareEnv()
|
||||
{
|
||||
Console.WriteLine("[硬件] 海康驱动预热中...");
|
||||
HikNativeMethods.NET_DVR_Init();
|
||||
HikSdkManager.ForceWarmUp();
|
||||
}
|
||||
|
||||
static async Task AddTestDevices(CameraManager manager)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 1. 添加测试设备
|
||||
var config = new VideoSourceConfig
|
||||
{
|
||||
Id = 101,
|
||||
Brand = DeviceBrand.HikVision,
|
||||
IpAddress = "192.168.5.9",
|
||||
Port = 8000,
|
||||
Username = "admin",
|
||||
Password = "RRYFOA",
|
||||
StreamType = 0
|
||||
};
|
||||
manager.AddDevice(config);
|
||||
|
||||
var config2 = new VideoSourceConfig
|
||||
{
|
||||
Id = 102,
|
||||
Brand = DeviceBrand.HikVision,
|
||||
IpAddress = "172.16.41.20",
|
||||
Port = 8000,
|
||||
Username = "admin",
|
||||
Password = "abcd1234",
|
||||
StreamType = 0
|
||||
};
|
||||
manager.AddDevice(config2);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ... 这里保留你原本的添加测试设备代码 ...
|
||||
// var config = new VideoSourceConfig { ... }
|
||||
// manager.AddDevice(config);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user