WebAPI 支持摄像头启停控制、码流切换、审计日志的提供
This commit is contained in:
283
SHH.CameraSdk/Controllers/CamerasController.cs
Normal file
283
SHH.CameraSdk/Controllers/CamerasController.cs
Normal file
@@ -0,0 +1,283 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class CamerasController : ControllerBase
|
||||
{
|
||||
private readonly CameraManager _manager;
|
||||
|
||||
public CamerasController(CameraManager manager)
|
||||
{
|
||||
_manager = manager;
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 区域 A: 设备全生命周期管理 (CRUD)
|
||||
// ==========================================================================
|
||||
|
||||
/// <summary>
|
||||
/// 1. 获取所有设备清单
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
public IActionResult GetAll()
|
||||
{
|
||||
var devices = _manager.GetAllDevices().Select(d => new
|
||||
{
|
||||
d.Id,
|
||||
d.Config.IpAddress,
|
||||
d.Config.Name,
|
||||
Status = d.Status.ToString(),
|
||||
d.RealFps,
|
||||
d.TotalFrames
|
||||
});
|
||||
return Ok(devices);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 2. 新增设备 (写入配置并初始化)
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
public IActionResult Add([FromBody] CameraConfigDto dto)
|
||||
{
|
||||
if (_manager.GetDevice(dto.Id) != null)
|
||||
return Conflict($"设备ID {dto.Id} 已存在");
|
||||
|
||||
// DTO 转 Config (实际项目中建议用 AutoMapper)
|
||||
var config = MapToConfig(dto);
|
||||
|
||||
_manager.AddDevice(config); // 添加到内存池
|
||||
// 注意:此时 IsRunning 默认为 false,等待手动 Start 或 API 控制
|
||||
|
||||
return CreatedAtAction(nameof(GetAll), new { id = dto.Id }, dto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3. 编辑设备 (自动识别冷热更新)
|
||||
/// </summary>
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> Update(long id, [FromBody] CameraConfigDto dto)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (id != dto.Id) return BadRequest("ID 不匹配");
|
||||
|
||||
// 调用 Manager 的智能更新逻辑 (之前实现的 UpdateDeviceConfigAsync)
|
||||
await _manager.UpdateDeviceConfigAsync(id, MapToUpdateDto(dto));
|
||||
|
||||
return Ok(new { Success = true, Message = "配置已更新" });
|
||||
}
|
||||
catch (KeyNotFoundException) { return NotFound(); }
|
||||
catch (System.Exception ex) { return StatusCode(500, ex.Message); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 4. 删除设备 (销毁连接)
|
||||
/// </summary>
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> Remove(long id)
|
||||
{
|
||||
var device = _manager.GetDevice(id);
|
||||
if (device == null) return NotFound();
|
||||
|
||||
await device.StopAsync(); // 停流
|
||||
_manager.RemoveDevice(id); // 从池中移除
|
||||
|
||||
return Ok($"设备 {id} 已移除");
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 区域 B: 多进程流控订阅 (Subscription Strategy)
|
||||
// ==========================================================================
|
||||
|
||||
/// <summary>
|
||||
/// 5. 注册/更新进程的流需求 (A/B/C/D 场景核心)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 示例场景:
|
||||
/// - 主进程配置(B): { "appId": "Main_Config", "displayFps": 25, "analysisFps": 0 }
|
||||
/// - AI进程(C): { "appId": "AI_Core", "displayFps": 0, "analysisFps": 5 }
|
||||
/// </remarks>
|
||||
[HttpPost("{id}/subscriptions")]
|
||||
public IActionResult UpdateSubscription(long id, [FromBody] SubscriptionDto sub)
|
||||
{
|
||||
var device = _manager.GetDevice(id);
|
||||
if (device == null) return NotFound();
|
||||
|
||||
// 逻辑转换:将 "显示帧" 和 "分析帧" 映射到底层控制器的注册表
|
||||
|
||||
// 1. 处理显示需求
|
||||
string displayKey = $"{sub.AppId}_Display";
|
||||
if (sub.DisplayFps > 0)
|
||||
{
|
||||
// 告诉控制器:这个 App 需要 X 帧用于显示
|
||||
device.Controller.Register(displayKey, sub.DisplayFps);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果不需要,移除注册
|
||||
device.Controller.Unregister(displayKey);
|
||||
}
|
||||
|
||||
// 2. 处理分析需求
|
||||
string analysisKey = $"{sub.AppId}_Analysis";
|
||||
if (sub.AnalysisFps > 0)
|
||||
{
|
||||
// 告诉控制器:这个 App 需要 Y 帧用于分析
|
||||
device.Controller.Register(analysisKey, sub.AnalysisFps);
|
||||
}
|
||||
else
|
||||
{
|
||||
device.Controller.Unregister(analysisKey);
|
||||
}
|
||||
|
||||
// 运维审计
|
||||
device.AddAuditLog($"更新订阅策略 [{sub.AppId}]: Display={sub.DisplayFps}, Analysis={sub.AnalysisFps}");
|
||||
|
||||
return Ok(new { Message = "订阅策略已更新", DeviceId = id });
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 区域 C: 句柄动态绑定 (Handle Binding)
|
||||
// ==========================================================================
|
||||
|
||||
/// <summary>
|
||||
/// 6. 绑定显示窗口 (对应 A进程-句柄场景)
|
||||
/// </summary>
|
||||
[HttpPost("{id}/bind-handle")]
|
||||
public IActionResult BindHandle(long id, [FromBody] BindHandleDto dto)
|
||||
{
|
||||
var device = _manager.GetDevice(id);
|
||||
if (device == null) return NotFound();
|
||||
|
||||
// 构造动态选项,应用句柄
|
||||
var options = new DynamicStreamOptions
|
||||
{
|
||||
RenderHandle = (System.IntPtr)dto.Handle
|
||||
};
|
||||
|
||||
device.ApplyOptions(options); // 触发驱动层的 OnApplyOptions
|
||||
|
||||
device.AddAuditLog($"绑定新句柄: {dto.Handle} ({dto.Purpose})");
|
||||
|
||||
return Ok(new { Success = true });
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 区域 D: 设备运行控制
|
||||
// ==========================================================================
|
||||
|
||||
/// <summary>
|
||||
/// 手动控制设备运行状态 (开关机)
|
||||
/// </summary>
|
||||
[HttpPost("{id}/power")]
|
||||
public async Task<IActionResult> TogglePower(long id, [FromQuery] bool enabled)
|
||||
{
|
||||
var device = _manager.GetDevice(id);
|
||||
if (device == null) return NotFound();
|
||||
|
||||
// 1. 更新运行意图
|
||||
device.IsRunning = enabled;
|
||||
|
||||
// 2. 审计与执行
|
||||
if (enabled)
|
||||
{
|
||||
device.AddAuditLog("用户指令:手动开启设备");
|
||||
// 异步启动,Coordinator 也会在下个周期辅助检查
|
||||
_ = device.StartAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
device.AddAuditLog("用户指令:手动关闭设备");
|
||||
await device.StopAsync();
|
||||
}
|
||||
|
||||
return Ok(new { DeviceId = id, IsRunning = enabled });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 热应用动态参数 (如切换码流)
|
||||
/// </summary>
|
||||
[HttpPost("{id}/options")]
|
||||
public IActionResult ApplyOptions(long id, [FromBody] DynamicStreamOptions options)
|
||||
{
|
||||
var device = _manager.GetDevice(id);
|
||||
if (device == null) return NotFound();
|
||||
|
||||
// 1. 如果涉及码流切换,先同步更新配置对象
|
||||
if (options.StreamType.HasValue)
|
||||
{
|
||||
var newConfig = device.Config.DeepCopy();
|
||||
newConfig.StreamType = options.StreamType.Value;
|
||||
device.UpdateConfig(newConfig);
|
||||
}
|
||||
|
||||
// 2. 应用到驱动层(触发热切换逻辑)
|
||||
device.ApplyOptions(options);
|
||||
|
||||
return Ok(new { Message = "动态参数已发送", DeviceId = id });
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 辅助方法 (Mapping)
|
||||
// ==========================================================================
|
||||
private VideoSourceConfig MapToConfig(CameraConfigDto dto)
|
||||
{
|
||||
return new VideoSourceConfig
|
||||
{
|
||||
Id = dto.Id,
|
||||
Name = dto.Name,
|
||||
Brand = (DeviceBrand)dto.Brand,
|
||||
IpAddress = dto.IpAddress,
|
||||
Port = dto.Port,
|
||||
Username = dto.Username,
|
||||
Password = dto.Password,
|
||||
ChannelIndex = dto.ChannelIndex,
|
||||
StreamType = dto.StreamType,
|
||||
RtspPath = dto.RtspPath
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// [辅助方法] 将全量配置 DTO 转换为更新 DTO
|
||||
/// </summary>
|
||||
private DeviceUpdateDto MapToUpdateDto(CameraConfigDto dto)
|
||||
{
|
||||
return new DeviceUpdateDto
|
||||
{
|
||||
// ==========================================
|
||||
// 1. 冷更新参数 (基础连接信息)
|
||||
// ==========================================
|
||||
IpAddress = dto.IpAddress,
|
||||
Port = dto.Port,
|
||||
Username = dto.Username,
|
||||
Password = dto.Password,
|
||||
ChannelIndex = dto.ChannelIndex,
|
||||
Brand = dto.Brand,
|
||||
RtspPath = dto.RtspPath,
|
||||
MainboardIp = dto.MainboardIp,
|
||||
MainboardPort = dto.MainboardPort,
|
||||
|
||||
// ==========================================
|
||||
// 2. 热更新参数 (运行时属性)
|
||||
// ==========================================
|
||||
Name = dto.Name,
|
||||
Location = dto.Location,
|
||||
StreamType = dto.StreamType,
|
||||
|
||||
// 注意:通常句柄是通过 bind-handle 接口单独绑定的,
|
||||
// 但如果 ConfigDto 里包含了上次保存的句柄,也可以映射
|
||||
// RenderHandle = dto.RenderHandle,
|
||||
|
||||
// ==========================================
|
||||
// 3. 图像处理参数
|
||||
// ==========================================
|
||||
AllowCompress = dto.AllowCompress,
|
||||
AllowExpand = dto.AllowExpand,
|
||||
TargetResolution = dto.TargetResolution,
|
||||
EnhanceImage = dto.EnhanceImage,
|
||||
UseGrayscale = dto.UseGrayscale
|
||||
};
|
||||
}
|
||||
}
|
||||
23
SHH.CameraSdk/Controllers/Dto/BindHandleDto.cs
Normal file
23
SHH.CameraSdk/Controllers/Dto/BindHandleDto.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// 句柄绑定 DTO
|
||||
/// 用于前端向后端传递窗口渲染句柄,实现视频流的硬件解码渲染
|
||||
/// </summary>
|
||||
public class BindHandleDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 窗口句柄 (IntPtr 转换为 long 类型传输,避免跨平台序列化问题)
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "渲染窗口句柄不能为空")]
|
||||
[Range(1, long.MaxValue, ErrorMessage = "句柄必须为有效的非负整数")]
|
||||
public long Handle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用途描述 (用于审计日志,如 "Main_Preview"、"AI_Analysis_Window")
|
||||
/// </summary>
|
||||
[MaxLength(64, ErrorMessage = "用途描述长度不能超过 64 个字符")]
|
||||
public string Purpose { get; set; } = string.Empty;
|
||||
}
|
||||
123
SHH.CameraSdk/Controllers/Dto/CameraConfigDto.cs
Normal file
123
SHH.CameraSdk/Controllers/Dto/CameraConfigDto.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
// ==============================================================================
|
||||
// 1. 物理与运行配置 DTO (对应 CRUD 操作)
|
||||
// 用于设备新增/全量配置查询,包含基础身份、连接信息、运行参数等全量字段
|
||||
// ==============================================================================
|
||||
public class CameraConfigDto
|
||||
{
|
||||
// --- 基础身份 (Identity) ---
|
||||
/// <summary>
|
||||
/// 设备唯一标识
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "设备ID不能为空")]
|
||||
[Range(1, long.MaxValue, ErrorMessage = "设备ID必须为正整数")]
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备友好名称
|
||||
/// </summary>
|
||||
[MaxLength(64, ErrorMessage = "设备名称长度不能超过64个字符")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 摄像头品牌类型 (0:HikVision, 1:Dahua, 2:RTSP...)
|
||||
/// </summary>
|
||||
[Range(0, 10, ErrorMessage = "品牌类型值必须在0-10范围内")]
|
||||
public int Brand { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备安装位置描述
|
||||
/// </summary>
|
||||
[MaxLength(128, ErrorMessage = "安装位置长度不能超过128个字符")]
|
||||
public string Location { get; set; } = string.Empty;
|
||||
|
||||
// --- 核心连接 (Connectivity) - 修改此类参数触发冷重启 ---
|
||||
/// <summary>
|
||||
/// 摄像头IP地址
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "IP地址不能为空")]
|
||||
[RegularExpression(@"^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$",
|
||||
ErrorMessage = "请输入合法的IPv4地址")]
|
||||
public string IpAddress { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// SDK端口 (如海康默认8000)
|
||||
/// </summary>
|
||||
[Range(1, 65535, ErrorMessage = "端口号必须在1-65535范围内")]
|
||||
public ushort Port { get; set; } = 8000;
|
||||
|
||||
/// <summary>
|
||||
/// 登录用户名
|
||||
/// </summary>
|
||||
[MaxLength(32, ErrorMessage = "用户名长度不能超过32个字符")]
|
||||
public string Username { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 登录密码
|
||||
/// </summary>
|
||||
[MaxLength(64, ErrorMessage = "密码长度不能超过64个字符")]
|
||||
public string Password { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 通道号 (通常为1)
|
||||
/// </summary>
|
||||
[Range(1, 32, ErrorMessage = "通道号必须在1-32范围内")]
|
||||
public int ChannelIndex { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// RTSP流路径 (备用或非SDK模式使用)
|
||||
/// </summary>
|
||||
[MaxLength(256, ErrorMessage = "RTSP地址长度不能超过256个字符")]
|
||||
public string RtspPath { get; set; } = string.Empty;
|
||||
|
||||
// --- 主板关联信息 (Metadata) ---
|
||||
/// <summary>
|
||||
/// 关联主板IP地址
|
||||
/// </summary>
|
||||
[RegularExpression(@"^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)?$",
|
||||
ErrorMessage = "请输入合法的IPv4地址")]
|
||||
public string MainboardIp { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 关联主板端口
|
||||
/// </summary>
|
||||
[Range(1, 65535, ErrorMessage = "主板端口号必须在1-65535范围内")]
|
||||
public int MainboardPort { get; set; } = 80;
|
||||
|
||||
// --- 运行时参数 (Runtime Options) - 支持热更新 ---
|
||||
/// <summary>
|
||||
/// 码流类型 (0:主码流, 1:子码流)
|
||||
/// </summary>
|
||||
[Range(0, 1, ErrorMessage = "码流类型只能是0(主码流)或1(子码流)")]
|
||||
public int StreamType { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 是否使用灰度图 (用于AI分析场景加速)
|
||||
/// </summary>
|
||||
public bool UseGrayscale { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用图像增强 (去噪/锐化等)
|
||||
/// </summary>
|
||||
public bool EnhanceImage { get; set; } = true;
|
||||
|
||||
// --- 画面变换 (Transform) - 支持热更新 ---
|
||||
/// <summary>
|
||||
/// 是否允许图像压缩 (降低带宽占用)
|
||||
/// </summary>
|
||||
public bool AllowCompress { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 是否允许图像放大 (提升渲染质量)
|
||||
/// </summary>
|
||||
public bool AllowExpand { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 目标分辨率 (格式如 1920x1080,空则保持原图)
|
||||
/// </summary>
|
||||
[RegularExpression(@"^\d+x\d+$", ErrorMessage = "分辨率格式必须为 宽度x高度 (如 1920x1080)")]
|
||||
public string TargetResolution { get; set; } = string.Empty;
|
||||
}
|
||||
96
SHH.CameraSdk/Controllers/Dto/DeviceUpdateDto.cs
Normal file
96
SHH.CameraSdk/Controllers/Dto/DeviceUpdateDto.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// 设备配置更新传输对象
|
||||
/// <para>用于接收前端的编辑请求,支持部分更新(字段为 null 表示不修改)</para>
|
||||
/// <para>自动区分 <b>冷更新参数</b>(需重启连接)和 <b>热更新参数</b>(无感生效)</para>
|
||||
/// </summary>
|
||||
public class DeviceUpdateDto
|
||||
{
|
||||
// ==============================================================================
|
||||
// 1. 冷更新参数 (Cold Update)
|
||||
// 修改此类参数涉及物理连接变更,后端会自动执行 "Stop -> Update -> Start" 流程
|
||||
// ==============================================================================
|
||||
|
||||
/// <summary>摄像头IP地址</summary>
|
||||
[RegularExpression(@"^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$",
|
||||
ErrorMessage = "请输入合法的IPv4地址")]
|
||||
public string? IpAddress { get; set; }
|
||||
|
||||
/// <summary>SDK端口 (如海康 8000)</summary>
|
||||
[Range(1, 65535, ErrorMessage = "端口号必须在 1-65535 范围内")]
|
||||
public ushort? Port { get; set; }
|
||||
|
||||
/// <summary>登录用户名</summary>
|
||||
[MaxLength(32, ErrorMessage = "用户名长度不能超过 32 个字符")]
|
||||
public string? Username { get; set; }
|
||||
|
||||
/// <summary>登录密码</summary>
|
||||
[MaxLength(64, ErrorMessage = "密码长度不能超过 64 个字符")]
|
||||
public string? Password { get; set; }
|
||||
|
||||
/// <summary>通道号 (默认 1)</summary>
|
||||
[Range(1, 32, ErrorMessage = "通道号必须在 1-32 范围内")]
|
||||
public int? ChannelIndex { get; set; }
|
||||
|
||||
/// <summary>摄像头品牌类型 (0:Hik, 1:Dahua...)</summary>
|
||||
[Range(0, 10, ErrorMessage = "品牌类型值必须在 0-10 范围内")]
|
||||
public int? Brand { get; set; }
|
||||
|
||||
/// <summary>RTSP流地址 (非SDK模式下使用)</summary>
|
||||
[MaxLength(256, ErrorMessage = "RTSP地址长度不能超过 256 个字符")]
|
||||
public string? RtspPath { get; set; }
|
||||
|
||||
/// <summary>关联的主板IP (用于联动控制)</summary>
|
||||
[RegularExpression(@"^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)?$",
|
||||
ErrorMessage = "请输入合法的IPv4地址")]
|
||||
public string? MainboardIp { get; set; }
|
||||
|
||||
/// <summary>关联的主板端口</summary>
|
||||
[Range(1, 65535, ErrorMessage = "主板端口号必须在 1-65535 范围内")]
|
||||
public int? MainboardPort { get; set; }
|
||||
|
||||
// ==============================================================================
|
||||
// 2. 热更新参数 (Hot Update)
|
||||
// 修改此类参数仅刷新运行时状态,不中断物理连接,无感生效
|
||||
// ==============================================================================
|
||||
|
||||
/// <summary>设备名称/别名</summary>
|
||||
[MaxLength(64, ErrorMessage = "设备名称长度不能超过 64 个字符")]
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>安装位置描述</summary>
|
||||
[MaxLength(128, ErrorMessage = "安装位置长度不能超过 128 个字符")]
|
||||
public string? Location { get; set; }
|
||||
|
||||
/// <summary>码流类型 (0:主码流, 1:子码流)</summary>
|
||||
[Range(0, 1, ErrorMessage = "码流类型只能是 0(主码流) 或 1(子码流)")]
|
||||
public int? StreamType { get; set; }
|
||||
|
||||
/// <summary>渲染句柄 (IntPtr 的 Long 形式)</summary>
|
||||
[Range(0, long.MaxValue, ErrorMessage = "渲染句柄必须是非负整数")]
|
||||
public long? RenderHandle { get; set; }
|
||||
|
||||
// ==============================================================================
|
||||
// 3. 图像处理参数 (Image Processing - Hot Update)
|
||||
// 影响解码后的 SmartFrame 数据格式
|
||||
// ==============================================================================
|
||||
|
||||
/// <summary>是否允许压缩 (影响传输带宽)</summary>
|
||||
public bool? AllowCompress { get; set; }
|
||||
|
||||
/// <summary>是否允许放大 (影响渲染质量)</summary>
|
||||
public bool? AllowExpand { get; set; }
|
||||
|
||||
/// <summary>目标分辨率 (格式如 "1920x1080",空则保持原图)</summary>
|
||||
[RegularExpression(@"^\d+x\d+$", ErrorMessage = "分辨率格式必须为 宽度x高度 (如 1920x1080)")]
|
||||
public string? TargetResolution { get; set; }
|
||||
|
||||
/// <summary>是否启用图像增强 (去噪/锐化等)</summary>
|
||||
public bool? EnhanceImage { get; set; }
|
||||
|
||||
/// <summary>是否转为灰度图 (用于 AI 纯分析场景加速)</summary>
|
||||
public bool? UseGrayscale { get; set; }
|
||||
}
|
||||
31
SHH.CameraSdk/Controllers/Dto/SubscriptionDto.cs
Normal file
31
SHH.CameraSdk/Controllers/Dto/SubscriptionDto.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
// ==============================================================================
|
||||
// 2. 订阅策略 DTO (对应 A/B/C/D 进程需求)
|
||||
// 用于多进程帧需求的注册与更新,支持显示帧和分析帧的独立配置
|
||||
// ==============================================================================
|
||||
public class SubscriptionDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 进程唯一标识 (如 "AI_Process_01"、"Main_Display_02")
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "进程标识 AppId 不能为空")]
|
||||
[MaxLength(50, ErrorMessage = "AppId 长度不能超过 50 个字符")]
|
||||
public string AppId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 显示帧率需求 (单位: fps)
|
||||
/// <para>不需要显示则设为 0,控制器会自动注销该类型需求</para>
|
||||
/// </summary>
|
||||
[Range(0, 60, ErrorMessage = "显示帧率需在 0-60 fps 范围内")]
|
||||
public int DisplayFps { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 分析帧率需求 (单位: fps)
|
||||
/// <para>不需要分析则设为 0,控制器会自动注销该类型需求</para>
|
||||
/// </summary>
|
||||
[Range(0, 30, ErrorMessage = "分析帧率需在 0-30 fps 范围内")]
|
||||
public int AnalysisFps { get; set; }
|
||||
}
|
||||
@@ -96,5 +96,28 @@ public class MonitorController : ControllerBase
|
||||
return File(imageBytes, "image/jpeg");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定相机的诊断信息(含审计日志)
|
||||
/// </summary>
|
||||
/// <param name="id">相机设备唯一标识</param>
|
||||
/// <returns>200 OK + 诊断信息 | 404 Not Found</returns>
|
||||
[HttpGet("diagnose/{id}")]
|
||||
public IActionResult GetDeviceDiagnostic(long id)
|
||||
{
|
||||
var device = _cameraManager.GetDevice(id);
|
||||
if (device == null) return NotFound();
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
Id = device.Id,
|
||||
Status = device.Status.ToString(),
|
||||
RealFps = device.RealFps,
|
||||
TotalFrames = device.TotalFrames,
|
||||
// 关键:将 BaseVideoSource 中的日志列表返回给前端
|
||||
// 注意:属性名 AuditLogs 会被序列化为 auditLogs (首字母小写),符合前端预期
|
||||
AuditLogs = device.GetAuditLogs()
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user