2025-12-26 03:18:21 +08:00
|
|
|
|
using Microsoft.AspNetCore.Http;
|
|
|
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
|
|
|
|
|
|
|
|
namespace SHH.CameraSdk;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 视频源实时状态监控 API 控制器
|
2025-12-26 23:05:08 +08:00
|
|
|
|
/// 核心功能:提供相机设备遥测数据查询、单设备详情查询、系统日志查询
|
2025-12-26 03:18:21 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
[ApiController]
|
2025-12-27 14:16:50 +08:00
|
|
|
|
[Route("api/[controller]")]
|
2025-12-26 03:18:21 +08:00
|
|
|
|
public class MonitorController : ControllerBase
|
|
|
|
|
|
{
|
|
|
|
|
|
#region --- 依赖注入 (Dependency Injection) ---
|
|
|
|
|
|
|
|
|
|
|
|
private readonly CameraManager _cameraManager;
|
2025-12-26 23:05:08 +08:00
|
|
|
|
private readonly IStorageService _storage; // [新增] 存储服务引用
|
2025-12-27 14:16:50 +08:00
|
|
|
|
private readonly ProcessingConfigManager _configManager;
|
2025-12-26 03:18:21 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2025-12-26 23:05:08 +08:00
|
|
|
|
/// 构造函数:注入 CameraManager 和 IStorageService
|
2025-12-26 03:18:21 +08:00
|
|
|
|
/// </summary>
|
2025-12-27 14:16:50 +08:00
|
|
|
|
public MonitorController(CameraManager cameraManager, IStorageService storage, ProcessingConfigManager configManager)
|
2025-12-26 03:18:21 +08:00
|
|
|
|
{
|
|
|
|
|
|
_cameraManager = cameraManager;
|
2025-12-26 23:05:08 +08:00
|
|
|
|
_storage = storage;
|
2025-12-27 14:16:50 +08:00
|
|
|
|
_configManager = configManager;
|
2025-12-26 03:18:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region --- API 接口定义 (API Endpoints) ---
|
|
|
|
|
|
|
2025-12-27 14:16:50 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取所有设备及配置 (对应前端 /api/Monitor/all)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[HttpGet("all")] // <--- 必须明确写上 "all",否则前端找不到
|
|
|
|
|
|
public IActionResult GetAll()
|
|
|
|
|
|
{
|
|
|
|
|
|
var cameras = _cameraManager.GetAllDevices();
|
|
|
|
|
|
var list = cameras.Select(c => new {
|
|
|
|
|
|
c.Id,
|
|
|
|
|
|
Status = c.Status.ToString(),
|
|
|
|
|
|
c.IsPhysicalOnline,
|
|
|
|
|
|
c.RealFps,
|
2025-12-28 08:07:55 +08:00
|
|
|
|
c.Width,
|
|
|
|
|
|
c.Height,
|
2025-12-27 14:16:50 +08:00
|
|
|
|
c.TotalFrames,
|
|
|
|
|
|
c.Config.Name,
|
|
|
|
|
|
c.Config.IpAddress,
|
|
|
|
|
|
// 务必包含配置信息,供前端回显
|
|
|
|
|
|
ProcessingOptions = _configManager.GetOptions(c.Id)
|
|
|
|
|
|
});
|
|
|
|
|
|
return Ok(list);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-26 03:18:21 +08:00
|
|
|
|
/// <summary>
|
2025-12-26 23:05:08 +08:00
|
|
|
|
/// 获取全量相机实时遥测数据快照
|
2025-12-26 03:18:21 +08:00
|
|
|
|
/// 适用场景:监控大屏首页数据看板
|
2025-12-26 23:05:08 +08:00
|
|
|
|
/// </summary>
|
2025-12-26 03:18:21 +08:00
|
|
|
|
[HttpGet("dashboard")]
|
|
|
|
|
|
public IActionResult GetDashboard()
|
|
|
|
|
|
{
|
|
|
|
|
|
var telemetrySnapshot = _cameraManager.GetTelemetrySnapshot();
|
|
|
|
|
|
return Ok(telemetrySnapshot);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取指定相机的详细运行指标
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[HttpGet("{id}")]
|
|
|
|
|
|
public IActionResult GetDeviceDetail(long id)
|
|
|
|
|
|
{
|
2025-12-28 08:07:55 +08:00
|
|
|
|
var d = _cameraManager.GetDevice(id);
|
|
|
|
|
|
if (d == null) return NotFound($"设备 ID: {id} 不存在");
|
2025-12-26 03:18:21 +08:00
|
|
|
|
|
2025-12-26 23:05:08 +08:00
|
|
|
|
return Ok(new
|
2025-12-26 03:18:21 +08:00
|
|
|
|
{
|
2025-12-28 08:07:55 +08:00
|
|
|
|
d.Id,
|
|
|
|
|
|
Status = d.Status.ToString(),
|
|
|
|
|
|
d.IsOnline,
|
|
|
|
|
|
d.IsPhysicalOnline,
|
|
|
|
|
|
d.RealFps,
|
|
|
|
|
|
d.Width,
|
|
|
|
|
|
d.Height,
|
|
|
|
|
|
d.TotalFrames,
|
|
|
|
|
|
d.Config.Name,
|
|
|
|
|
|
d.Config.IpAddress,
|
2025-12-27 14:16:50 +08:00
|
|
|
|
// --- 新增:将内存中的订阅需求列表传给前端 ---
|
2025-12-28 08:07:55 +08:00
|
|
|
|
Requirements = d.Controller.GetCurrentRequirements().Select(r => new {
|
2025-12-27 14:16:50 +08:00
|
|
|
|
r.AppId,
|
|
|
|
|
|
r.TargetFps,
|
2025-12-28 08:07:55 +08:00
|
|
|
|
r.LastActive,
|
|
|
|
|
|
r.RealFps,
|
|
|
|
|
|
r.Memo,
|
|
|
|
|
|
r.SavePath,
|
|
|
|
|
|
r.Handle,
|
|
|
|
|
|
r.TargetIp,
|
|
|
|
|
|
r.TargetPort,
|
|
|
|
|
|
r.Protocol,
|
|
|
|
|
|
r.Type,
|
2025-12-27 14:16:50 +08:00
|
|
|
|
})
|
2025-12-26 23:05:08 +08:00
|
|
|
|
});
|
2025-12-26 03:18:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取指定相机的实时截图
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[HttpGet("snapshot/{id}")]
|
|
|
|
|
|
public async Task<IActionResult> GetSnapshot(long id)
|
|
|
|
|
|
{
|
2025-12-26 23:05:08 +08:00
|
|
|
|
// 假设您有 SnapshotCoordinator 单例,此处保留原逻辑
|
2025-12-26 03:18:21 +08:00
|
|
|
|
var imageBytes = await SnapshotCoordinator.Instance.RequestSnapshotAsync(id, 2000);
|
|
|
|
|
|
|
|
|
|
|
|
if (imageBytes == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return StatusCode(StatusCodes.Status504GatewayTimeout, "截图请求超时或设备未响应");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return File(imageBytes, "image/jpeg");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-26 12:15:10 +08:00
|
|
|
|
/// <summary>
|
2025-12-26 23:05:08 +08:00
|
|
|
|
/// 获取指定相机的深度诊断信息(包含持久化的审计日志)
|
2025-12-26 12:15:10 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
[HttpGet("diagnose/{id}")]
|
2025-12-26 23:05:08 +08:00
|
|
|
|
public async Task<IActionResult> GetDeviceDiagnostic(long id)
|
2025-12-26 12:15:10 +08:00
|
|
|
|
{
|
|
|
|
|
|
var device = _cameraManager.GetDevice(id);
|
2025-12-26 23:05:08 +08:00
|
|
|
|
if (device == null) return NotFound("设备不存在");
|
|
|
|
|
|
|
|
|
|
|
|
// [修正] 改为从 StorageService 读取文件日志
|
|
|
|
|
|
// 这样即使重启程序,历史日志也能查到
|
|
|
|
|
|
var logs = await _storage.GetDeviceLogsAsync((int)id, 50);
|
2025-12-26 12:15:10 +08:00
|
|
|
|
|
|
|
|
|
|
return Ok(new
|
|
|
|
|
|
{
|
2025-12-26 23:05:08 +08:00
|
|
|
|
// 基础信息
|
2025-12-26 12:15:10 +08:00
|
|
|
|
Id = device.Id,
|
|
|
|
|
|
Status = device.Status.ToString(),
|
|
|
|
|
|
RealFps = device.RealFps,
|
|
|
|
|
|
TotalFrames = device.TotalFrames,
|
2025-12-26 23:05:08 +08:00
|
|
|
|
|
|
|
|
|
|
// 实时状态
|
|
|
|
|
|
BasicInfo = device.Config,
|
|
|
|
|
|
RealTimeStats = new
|
|
|
|
|
|
{
|
|
|
|
|
|
device.RealFps,
|
|
|
|
|
|
device.TotalFrames,
|
|
|
|
|
|
device.IsPhysicalOnline,
|
|
|
|
|
|
device.Width,
|
|
|
|
|
|
device.Height
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// [关键] 持久化日志
|
|
|
|
|
|
AuditLogs = logs
|
2025-12-26 12:15:10 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-26 03:18:21 +08:00
|
|
|
|
#endregion
|
2025-12-26 18:59:27 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取系统操作日志(读取最新的 50 条)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[HttpGet("system-logs")]
|
2025-12-26 23:05:08 +08:00
|
|
|
|
public async Task<IActionResult> GetSystemLogs()
|
2025-12-26 18:59:27 +08:00
|
|
|
|
{
|
2025-12-26 23:05:08 +08:00
|
|
|
|
// [修正] 彻底废弃手动读文件,改用 Service
|
|
|
|
|
|
// Service 内部会自动处理锁、路径 (App_Data/Process_X/system.log) 和异常
|
|
|
|
|
|
var logs = await _storage.GetSystemLogsAsync(50);
|
|
|
|
|
|
|
|
|
|
|
|
return Ok(logs);
|
2025-12-26 18:59:27 +08:00
|
|
|
|
}
|
2025-12-27 14:16:50 +08:00
|
|
|
|
|
|
|
|
|
|
[HttpPost("update-processing")]
|
|
|
|
|
|
public IActionResult UpdateProcessing([FromBody] UpdateProcessingRequest request)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 1. 验证设备是否存在 (可选)
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 构造配置对象
|
|
|
|
|
|
var options = new ProcessingOptions
|
|
|
|
|
|
{
|
|
|
|
|
|
EnableShrink = request.EnableShrink,
|
|
|
|
|
|
EnableExpand = request.EnableExpand,
|
|
|
|
|
|
TargetWidth = request.TargetWidth,
|
|
|
|
|
|
TargetHeight = request.TargetHeight,
|
2025-12-28 08:07:55 +08:00
|
|
|
|
EnableBrightness = request.EnableBrightness,
|
|
|
|
|
|
Brightness = request.Brightness
|
2025-12-27 14:16:50 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 提交给配置管理器 (实时生效)
|
|
|
|
|
|
// 这里的 _configManager 是通过构造函数注入的单例
|
|
|
|
|
|
_configManager.UpdateOptions(request.DeviceId, options);
|
|
|
|
|
|
|
|
|
|
|
|
return Ok(new { msg = $"设备 {request.DeviceId} 配置已更新", time = DateTime.Now });
|
|
|
|
|
|
}
|
2025-12-26 03:18:21 +08:00
|
|
|
|
}
|