规范并补充日志内容
This commit is contained in:
@@ -1,28 +0,0 @@
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
public interface IStorageService
|
||||
{
|
||||
// 1. 基础属性
|
||||
int ProcessId { get; }
|
||||
|
||||
// 2. 设备配置管理
|
||||
// 保存:接收 VideoSourceConfig 集合
|
||||
Task SaveDevicesAsync(IEnumerable<VideoSourceConfig> configs);
|
||||
|
||||
// 加载:返回 VideoSourceConfig 列表
|
||||
Task<List<VideoSourceConfig>> LoadDevicesAsync();
|
||||
|
||||
// 3. 系统日志
|
||||
// 记录系统操作 (如 POST /api/cameras)
|
||||
Task AppendSystemLogAsync(string action, string ip, string path);
|
||||
|
||||
// 获取系统日志
|
||||
Task<List<string>> GetSystemLogsAsync(int count);
|
||||
|
||||
// 4. 设备审计日志
|
||||
// 记录单设备日志 (统一使用 int deviceId)
|
||||
Task AppendDeviceLogAsync(int deviceId, string message);
|
||||
|
||||
// 获取单设备日志
|
||||
Task<List<string>> GetDeviceLogsAsync(int deviceId, int count);
|
||||
}
|
||||
@@ -14,16 +14,14 @@ public class MonitorController : ControllerBase
|
||||
#region --- 依赖注入 (Dependency Injection) ---
|
||||
|
||||
private readonly CameraManager _cameraManager;
|
||||
private readonly IStorageService _storage; // [新增] 存储服务引用
|
||||
private readonly ProcessingConfigManager _configManager;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数:注入 CameraManager 和 IStorageService
|
||||
/// </summary>
|
||||
public MonitorController(CameraManager cameraManager, IStorageService storage, ProcessingConfigManager configManager)
|
||||
public MonitorController(CameraManager cameraManager, ProcessingConfigManager configManager)
|
||||
{
|
||||
_cameraManager = cameraManager;
|
||||
_storage = storage;
|
||||
_configManager = configManager;
|
||||
}
|
||||
|
||||
@@ -131,7 +129,6 @@ public class MonitorController : ControllerBase
|
||||
|
||||
// [修正] 改为从 StorageService 读取文件日志
|
||||
// 这样即使重启程序,历史日志也能查到
|
||||
var logs = await _storage.GetDeviceLogsAsync((int)id, 50);
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
@@ -151,27 +148,11 @@ public class MonitorController : ControllerBase
|
||||
device.Width,
|
||||
device.Height
|
||||
},
|
||||
|
||||
// [关键] 持久化日志
|
||||
AuditLogs = logs
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 获取系统操作日志(读取最新的 50 条)
|
||||
/// </summary>
|
||||
[HttpGet("system-logs")]
|
||||
public async Task<IActionResult> GetSystemLogs()
|
||||
{
|
||||
// [修正] 彻底废弃手动读文件,改用 Service
|
||||
// Service 内部会自动处理锁、路径 (App_Data/Process_X/system.log) 和异常
|
||||
var logs = await _storage.GetSystemLogsAsync(50);
|
||||
|
||||
return Ok(logs);
|
||||
}
|
||||
|
||||
[HttpPost("update-processing")]
|
||||
public IActionResult UpdateProcessing([FromBody] UpdateProcessingRequest request)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using OpenCvSharp;
|
||||
using Ayay.SerilogLogs;
|
||||
using OpenCvSharp;
|
||||
using Serilog;
|
||||
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
@@ -13,6 +15,8 @@ public class FrameConsumer : IDisposable
|
||||
{
|
||||
#region --- 私有资源与状态 (Private Resources & States) ---
|
||||
|
||||
private static ILogger _sysLog = Log.ForContext("SourceContext", LogModules.Core);
|
||||
|
||||
/// <summary> 帧缓冲队列(容量1):仅存储最新一帧,保证零延迟渲染 </summary>
|
||||
/// <remarks> BlockingCollection 封装线程安全操作,GetConsumingEnumerable 支持取消令牌 </remarks>
|
||||
private readonly BlockingCollection<SmartFrame> _frameBuffer = new(1);
|
||||
@@ -49,7 +53,7 @@ public class FrameConsumer : IDisposable
|
||||
|
||||
// 启动长期运行的渲染任务,提升线程调度优先级
|
||||
_renderTask = Task.Factory.StartNew(RenderLoop, TaskCreationOptions.LongRunning);
|
||||
Console.WriteLine($"[Consumer] 渲染线程启动成功,窗口名称: {_windowName}");
|
||||
_sysLog.Information($"[Consumer] 渲染线程启动成功,窗口名称: {_windowName}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -75,7 +79,7 @@ public class FrameConsumer : IDisposable
|
||||
residualFrame.Dispose();
|
||||
}
|
||||
|
||||
Console.WriteLine($"[Consumer] 渲染线程已停止,窗口: {_windowName}");
|
||||
_sysLog.Information($"[Consumer] 渲染线程已停止,窗口: {_windowName}");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
namespace SHH.CameraSdk;
|
||||
using Ayay.SerilogLogs;
|
||||
using Serilog;
|
||||
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// [管理层] 视频源总控管理器 (V3.5 持久化集成版)
|
||||
@@ -8,6 +11,8 @@ public class CameraManager : IDisposable, IAsyncDisposable
|
||||
{
|
||||
#region --- 1. 核心资源与状态 (Fields & States) ---
|
||||
|
||||
private static ILogger _sysLog = Log.ForContext("SourceContext", LogModules.Core);
|
||||
|
||||
/// <summary> 全局设备实例池(线程安全),Key = 设备唯一标识 </summary>
|
||||
private readonly ConcurrentDictionary<long, BaseVideoSource> _cameraPool = new();
|
||||
|
||||
@@ -26,17 +31,13 @@ public class CameraManager : IDisposable, IAsyncDisposable
|
||||
/// </summary>
|
||||
private volatile bool _isEngineStarted = false;
|
||||
|
||||
// [新增] 存储服务引用
|
||||
private readonly IStorageService _storage;
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 构造函数 (Constructor) ---
|
||||
|
||||
// [修改] 注入 IStorageService
|
||||
public CameraManager(IStorageService storage)
|
||||
public CameraManager()
|
||||
{
|
||||
_storage = storage;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -59,17 +60,15 @@ public class CameraManager : IDisposable, IAsyncDisposable
|
||||
{
|
||||
// 如果添加失败(ID冲突),由于 device 还没被使用,直接释放掉
|
||||
device.DisposeAsync().AsTask().Wait();
|
||||
|
||||
_sysLog.Warning($"[Core] 设备 ID:{config.Id} 已存在");
|
||||
_sysLog.Debug($"[Core] 设备 ID:{config.Id} => 明细:" + "{@cfg}.", config);
|
||||
throw new InvalidOperationException($"设备 ID {config.Id} 已存在");
|
||||
}
|
||||
|
||||
// 动态激活逻辑:引擎已启动时,新设备直接标记为运行状态
|
||||
if (_isEngineStarted)
|
||||
{
|
||||
device.IsRunning = true;
|
||||
}
|
||||
|
||||
// [新增] 自动保存到文件
|
||||
SaveChanges();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -94,7 +93,7 @@ public class CameraManager : IDisposable, IAsyncDisposable
|
||||
if (_cameraPool.TryRemove(id, out var device))
|
||||
{
|
||||
// 记录日志
|
||||
Console.WriteLine($"[Manager] 正在移除设备 {id}...");
|
||||
_sysLog.Information("[Core] 正在移除设备, ID {0} ", id);
|
||||
|
||||
// 1. 停止物理连接
|
||||
await device.StopAsync();
|
||||
@@ -102,10 +101,7 @@ public class CameraManager : IDisposable, IAsyncDisposable
|
||||
// 2. 释放资源
|
||||
await device.DisposeAsync();
|
||||
|
||||
Console.WriteLine($"[Manager] 设备 {id} 已彻底移除");
|
||||
|
||||
// [新增] 自动保存到文件
|
||||
SaveChanges();
|
||||
_sysLog.Warning("[Core] 设备已彻底移除, ID {0} ", id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,36 +121,7 @@ public class CameraManager : IDisposable, IAsyncDisposable
|
||||
if (_isEngineStarted) return;
|
||||
|
||||
// =========================================================
|
||||
// 1. [新增] 从文件加载设备配置
|
||||
// =========================================================
|
||||
try
|
||||
{
|
||||
Console.WriteLine("[Manager] 正在检查本地配置文件...");
|
||||
var savedConfigs = await _storage.LoadDevicesAsync();
|
||||
|
||||
int loadedCount = 0;
|
||||
foreach (var config in savedConfigs)
|
||||
{
|
||||
// 防止ID冲突(虽然文件里理论上不重复)
|
||||
if (!_cameraPool.ContainsKey(config.Id))
|
||||
{
|
||||
var device = CreateDeviceInstance(config);
|
||||
// 默认设为运行状态,让协调器稍后去连接
|
||||
//device.IsRunning = true;
|
||||
_cameraPool.TryAdd(config.Id, device);
|
||||
loadedCount++;
|
||||
}
|
||||
}
|
||||
if (loadedCount > 0)
|
||||
Console.WriteLine($"[Manager] 已从文件恢复 {loadedCount} 台设备配置");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[Manager] 加载配置文件警告: {ex.Message}");
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// 2. 全局驱动环境预初始化
|
||||
// 1. 全局驱动环境预初始化
|
||||
// =========================================================
|
||||
HikSdkManager.Initialize();
|
||||
|
||||
@@ -162,7 +129,7 @@ public class CameraManager : IDisposable, IAsyncDisposable
|
||||
_isEngineStarted = true;
|
||||
|
||||
// =========================================================
|
||||
// 3. 启动协调器后台自愈循环
|
||||
// 2. 启动协调器后台自愈循环
|
||||
// =========================================================
|
||||
_ = Task.Factory.StartNew(
|
||||
() => _coordinator.RunCoordinationLoopAsync(_globalCts.Token),
|
||||
@@ -175,7 +142,7 @@ public class CameraManager : IDisposable, IAsyncDisposable
|
||||
// *注意*:如果 Coordinator 需要显式注册,请在这里补上:
|
||||
foreach (var dev in _cameraPool.Values) _coordinator.Register(dev);
|
||||
|
||||
Console.WriteLine($"[CameraManager] 引擎启动成功,当前管理 {_cameraPool.Count} 路相机设备。");
|
||||
_sysLog.Warning($"[Core] 设备管理引擎启动成功, 当前管理 {_cameraPool.Count} 路设备");
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -191,20 +158,6 @@ public class CameraManager : IDisposable, IAsyncDisposable
|
||||
|
||||
#region --- 4. 监控数据采集 (Telemetry Collection) ---
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有相机的健康度报告
|
||||
/// </summary>
|
||||
public IEnumerable<CameraHealthReport> GetDetailedTelemetry()
|
||||
{
|
||||
return _cameraPool.Values.Select(cam => new CameraHealthReport
|
||||
{
|
||||
DeviceId = cam.Id,
|
||||
Ip = cam.Config.IpAddress,
|
||||
Status = cam.Status.ToString(),
|
||||
LastError = cam.Status == VideoSourceStatus.Faulted ? "设备故障或网络中断" : "运行正常"
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取全量相机实时遥测数据快照 (MonitorController 使用)
|
||||
/// </summary>
|
||||
@@ -250,10 +203,13 @@ public class CameraManager : IDisposable, IAsyncDisposable
|
||||
public async Task UpdateDeviceConfigAsync(long deviceId, DeviceUpdateDto dto)
|
||||
{
|
||||
if (!_cameraPool.TryGetValue(deviceId, out var device))
|
||||
throw new KeyNotFoundException($"设备 {deviceId} 不存在");
|
||||
{
|
||||
_sysLog.Warning($"[Core] 设备更新制作, ID:{deviceId} 不存在.");
|
||||
throw new KeyNotFoundException($"设备 ID:{deviceId} 不存在.");
|
||||
}
|
||||
|
||||
// 1. 审计
|
||||
device.AddAuditLog("收到配置更新请求");
|
||||
_sysLog.Debug($"[Core] 响应设备配置更新请求, ID:{deviceId}.");
|
||||
|
||||
// 2. 创建副本进行对比
|
||||
var oldConfig = device.Config;
|
||||
@@ -287,7 +243,7 @@ public class CameraManager : IDisposable, IAsyncDisposable
|
||||
|
||||
if (needColdRestart)
|
||||
{
|
||||
device.AddAuditLog($"检测到核心参数变更,执行冷重启 (Reboot)");
|
||||
_sysLog.Debug($"[Core] 检测到核心参数变更, 执行冷重启, ID:{deviceId}.");
|
||||
bool wasRunning = device.IsRunning;
|
||||
|
||||
// A. 彻底停止
|
||||
@@ -301,7 +257,7 @@ public class CameraManager : IDisposable, IAsyncDisposable
|
||||
}
|
||||
else
|
||||
{
|
||||
device.AddAuditLog($"检测到运行时参数变更,执行热更新 (HotSwap)");
|
||||
_sysLog.Debug($"[Core] 检测到运行时参数变更, 执行热更新, ID:{deviceId}.");
|
||||
|
||||
// A. 更新配置数据
|
||||
device.UpdateConfig(newConfig);
|
||||
@@ -317,47 +273,6 @@ public class CameraManager : IDisposable, IAsyncDisposable
|
||||
device.ApplyOptions(options);
|
||||
}
|
||||
}
|
||||
|
||||
// [新增] 保存文件
|
||||
SaveChanges();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全量替换更新 (兼容接口)
|
||||
/// </summary>
|
||||
public async Task UpdateDeviceAsync(int id, VideoSourceConfig newConfig)
|
||||
{
|
||||
if (!_cameraPool.TryGetValue(id, out var oldDevice))
|
||||
throw new KeyNotFoundException($"设备 #{id} 不存在");
|
||||
|
||||
bool wasRunning = oldDevice.IsRunning ||
|
||||
oldDevice.Status == VideoSourceStatus.Playing ||
|
||||
oldDevice.Status == VideoSourceStatus.Connecting;
|
||||
|
||||
Console.WriteLine($"[Manager] 正在更新设备 #{id},配置变更中...");
|
||||
|
||||
try
|
||||
{
|
||||
await oldDevice.StopAsync();
|
||||
await oldDevice.DisposeAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[Manager] 销毁旧设备时警告: {ex.Message}");
|
||||
}
|
||||
|
||||
var newDevice = CreateDeviceInstance(newConfig);
|
||||
_cameraPool[id] = newDevice;
|
||||
|
||||
Console.WriteLine($"[Manager] 设备 #{id} 实例已重建。");
|
||||
|
||||
if (wasRunning)
|
||||
{
|
||||
await newDevice.StartAsync();
|
||||
}
|
||||
|
||||
// [新增] 保存文件
|
||||
SaveChanges();
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -366,6 +281,10 @@ public class CameraManager : IDisposable, IAsyncDisposable
|
||||
|
||||
public void Dispose() => DisposeAsync().AsTask().GetAwaiter().GetResult();
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
@@ -402,31 +321,38 @@ public class CameraManager : IDisposable, IAsyncDisposable
|
||||
|
||||
#region --- 7. 内部辅助 (Helpers) ---
|
||||
|
||||
/// <summary>
|
||||
/// 创建设备实例
|
||||
/// </summary>
|
||||
/// <param name="config"></param>
|
||||
/// <returns></returns>
|
||||
private BaseVideoSource CreateDeviceInstance(VideoSourceConfig config)
|
||||
{
|
||||
return config.Brand switch
|
||||
{
|
||||
DeviceBrand.HikVision => new HikVideoSource(config),
|
||||
_ => throw new NotSupportedException($"不支持的设备品牌: {config.Brand}")
|
||||
|
||||
// 使用模式匹配获取不匹配的值,记录详细的 DTO 上下文
|
||||
_ => HandleUnsupportedBrand(config)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// [新增] 触发异步保存 (Fire-and-Forget)
|
||||
/// 不阻塞当前 API 线程,让后台存储服务去排队写入
|
||||
/// 处理不支持的设备品牌
|
||||
/// </summary>
|
||||
private void SaveChanges()
|
||||
/// <param name="config"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotSupportedException"></exception>
|
||||
private BaseVideoSource HandleUnsupportedBrand(VideoSourceConfig config)
|
||||
{
|
||||
try
|
||||
{
|
||||
var allConfigs = _cameraPool.Values.Select(d => d.Config).ToList();
|
||||
// 异步调用存储服务,不使用 await 以免阻塞 API 响应
|
||||
_ = _storage.SaveDevicesAsync(allConfigs);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[Manager] 触发保存失败: {ex.Message}");
|
||||
}
|
||||
// 1. 构造错误消息
|
||||
string errorMsg = $"❌ 不支持的设备品牌: {config.Brand} (ID: {config.Id}, Name: {config.Name})";
|
||||
|
||||
// 2. 写入日志 - 建议带上 config 的解构信息,方便排查是否是前端传参错误
|
||||
_sysLog.Error($"[Core] {errorMsg} | 配置详情: " + "{@Config}", config);
|
||||
|
||||
// 3. 抛出异常,阻止程序进入不确定状态
|
||||
throw new NotSupportedException(errorMsg);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
namespace SHH.CameraSdk;
|
||||
using Ayay.SerilogLogs;
|
||||
using Serilog;
|
||||
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// 全局流分发器(静态类 | 线程安全)
|
||||
@@ -14,6 +17,8 @@ public static class GlobalStreamDispatcher
|
||||
{
|
||||
#region --- 1. 预设订阅通道 (Predefined Subscription Channels) ---
|
||||
|
||||
private static ILogger _sysLog = Log.ForContext("SourceContext", LogModules.Core);
|
||||
|
||||
/// <summary>
|
||||
/// UI 预览订阅通道:供 UI 模块订阅帧数据,用于实时画面显示
|
||||
/// 回调参数:(设备唯一标识, 处理后的智能帧数据)
|
||||
@@ -50,7 +55,7 @@ public static class GlobalStreamDispatcher
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[GlobalBus Error] 广播异常: {ex.Message}");
|
||||
_sysLog.Error($"[GlobalBus] 帧广播分发异常: {ex.Message}");
|
||||
}
|
||||
|
||||
// B. 执行你原有的定向分发逻辑 (给处理链用)
|
||||
@@ -151,7 +156,7 @@ public static class GlobalStreamDispatcher
|
||||
if (deviceMap.TryRemove(specificDeviceId, out _))
|
||||
{
|
||||
// 可选:如果该 AppId 下没设备了,是否清理外层字典?(为了性能通常不清理,或者定期清理)
|
||||
// Console.WriteLine($"[Dispatcher] {appId} 已停止订阅设备 {specificDeviceId}");
|
||||
_sysLog.Information($"[Dispatcher] {appId} 已停止订阅设备 ID:{specificDeviceId }");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -223,7 +228,7 @@ public static class GlobalStreamDispatcher
|
||||
{
|
||||
// 单个订阅者异常隔离,不影响其他分发流程
|
||||
task.Context.AddLog($"帧任务 [Seq:{sequence}] 投递到 AppId:{appId} 失败:{ex.Message}");
|
||||
Console.WriteLine($"[DispatchError] AppId={appId}, DeviceId={deviceId}, Error={ex.Message}");
|
||||
_sysLog.Error($"[Dispatch] AppId={appId}, DeviceId={deviceId}, Error={ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,7 +247,7 @@ public static class GlobalStreamDispatcher
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[DispatchError] DeviceSpecific AppId={appId}, Dev={deviceId}: {ex.Message}");
|
||||
_sysLog.Error($"[Dispatch] DeviceSpecific AppId={appId}, Dev={deviceId}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -285,7 +290,7 @@ public static class GlobalStreamDispatcher
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[SidecarDispatchError] App={sidecarAppId}, Dev={deviceId}: {ex.Message}");
|
||||
_sysLog.Error($"[Dispatch] App={sidecarAppId}, Dev={deviceId}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -317,7 +322,7 @@ public static class GlobalStreamDispatcher
|
||||
// TryRemove 是原子的、线程安全的
|
||||
if (_routingTable.TryRemove(appId, out _))
|
||||
{
|
||||
Console.WriteLine($"[Dispatcher] 已强制移除 AppId [{appId}] 的所有订阅路由");
|
||||
_sysLog.Error($"[Dispatcher] 已强制移除 AppId [{appId}] 的所有订阅路由.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
namespace SHH.CameraSdk;
|
||||
using Ayay.SerilogLogs;
|
||||
using Serilog;
|
||||
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// 帧处理管道(后台处理核心)
|
||||
@@ -12,6 +15,8 @@ public class ProcessingPipeline
|
||||
{
|
||||
#region --- 私有资源与状态 (Private Resources & States) ---
|
||||
|
||||
private static ILogger _sysLog = Log.ForContext("SourceContext", LogModules.Core);
|
||||
|
||||
/// <summary> 任务队列(有界通道):存储待处理的帧任务 </summary>
|
||||
private readonly Channel<ProcessingTask> _queue;
|
||||
|
||||
@@ -135,7 +140,7 @@ public class ProcessingPipeline
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 捕获处理过程中的异常,避免影响后续任务执行
|
||||
Console.WriteLine($"[PipelineError] 帧处理失败 (DeviceId: {task.DeviceId}, Seq: {task.Decision.Sequence}): {ex.Message}");
|
||||
_sysLog.Error($"[Pipeline] 帧处理失败 (DeviceId: {task.DeviceId}, Seq: {task.Decision.Sequence}): {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Net.NetworkInformation;
|
||||
using Ayay.SerilogLogs;
|
||||
using Serilog;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace SHH.CameraSdk;
|
||||
@@ -13,6 +15,8 @@ public class CameraCoordinator
|
||||
{
|
||||
#region --- 私有资源与配置 (Private Resources & Configurations) ---
|
||||
|
||||
private static ILogger _sysLog = Log.ForContext("SourceContext", LogModules.Core);
|
||||
|
||||
/// <summary> 已注册的相机设备集合(线程安全,支持并发添加与遍历) </summary>
|
||||
private readonly ConcurrentBag<BaseVideoSource> _cameras = new();
|
||||
|
||||
@@ -90,7 +94,7 @@ public class CameraCoordinator
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 捕获调度层全局异常,避免循环终止
|
||||
Console.WriteLine($"[CoordinatorCritical] 调度循环异常: {ex.Message}");
|
||||
_sysLog.Error($"[Coordinator] 调度循环异常: {ex.Message}");
|
||||
}
|
||||
|
||||
try
|
||||
@@ -186,7 +190,7 @@ public class CameraCoordinator
|
||||
// 【关键修复】:增加了 && cam.IsRunning 判定,防止待机状态下被误复位
|
||||
else if (isPhysicalOk && cam.IsOnline && !isFlowing && cam.IsRunning) // [cite: 504]
|
||||
{
|
||||
Console.WriteLine($"[自愈] 设备 {cam.Id} 僵死({secondsSinceLastFrame:F1}秒无帧),复位中...");
|
||||
_sysLog.Warning($"[Coordinator] [自愈] 设备 {cam.Id} 僵死({secondsSinceLastFrame:F1}秒无帧),复位中...");
|
||||
await cam.StopAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
namespace SHH.CameraSdk;
|
||||
using Ayay.SerilogLogs;
|
||||
using Serilog;
|
||||
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// 帧控制器(混合模式最终版)
|
||||
@@ -8,6 +11,8 @@
|
||||
/// </summary>
|
||||
public class FrameController
|
||||
{
|
||||
private static ILogger _sysLog = Log.ForContext("SourceContext", LogModules.Core);
|
||||
|
||||
// 需求字典
|
||||
private readonly ConcurrentDictionary<string, FrameRequirement> _requirements = new();
|
||||
|
||||
@@ -68,7 +73,7 @@ public class FrameController
|
||||
// 2. 从积分累加器中移除(防止内存泄漏)
|
||||
_accumulators.TryRemove(appId, out _);
|
||||
|
||||
Console.WriteLine($"[Scheduler] 已从调度中心彻底移除 AppId: {appId}");
|
||||
_sysLog.Warning($"[Core] 帧控制器已从调度中心彻底移除 AppId: {appId}");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
|
||||
@@ -22,46 +22,42 @@ namespace SHH.CameraSdk
|
||||
services.AddSingleton<ProcessingConfigManager>();
|
||||
|
||||
// =============================================================
|
||||
// 2. 图像处理流水线编排 (Pipeline)
|
||||
// 2. 图像处理流水线编排 (Pipeline) - 修复版
|
||||
// =============================================================
|
||||
// 这里我们利用 Factory 模式在注册时完成链条组装,保持了你原有的逻辑
|
||||
|
||||
// 1. 先注册下游节点 (Enhance)
|
||||
// 这样整个系统(包括 Controller 和 Scale)都共享这唯一的一个实例
|
||||
services.AddSingleton<ImageEnhanceCluster>(sp =>
|
||||
{
|
||||
var configMgr = sp.GetRequiredService<ProcessingConfigManager>();
|
||||
return new ImageEnhanceCluster(4, configMgr);
|
||||
});
|
||||
|
||||
// 2. 再注册上游节点 (Scale) 并完成组装
|
||||
services.AddSingleton<ImageScaleCluster>(sp =>
|
||||
{
|
||||
var configMgr = sp.GetRequiredService<ProcessingConfigManager>();
|
||||
|
||||
// 手动创建实例
|
||||
// 创建 Scale 实例
|
||||
var scale = new ImageScaleCluster(4, configMgr);
|
||||
var enhance = new ImageEnhanceCluster(4, configMgr);
|
||||
|
||||
// ★ 编排流水线:缩放 -> 增亮
|
||||
// ★ 关键修复:从容器中获取已经在上面注册好的 Enhance 实例
|
||||
// 而不是 new 一个新的
|
||||
var enhance = sp.GetRequiredService<ImageEnhanceCluster>();
|
||||
|
||||
// ★ 编排流水线:缩放 -> 增亮 (现在引用的是同一个对象了)
|
||||
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>();
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
namespace SHH.CameraSdk
|
||||
using Ayay.SerilogLogs;
|
||||
using Serilog;
|
||||
|
||||
namespace SHH.CameraSdk
|
||||
{
|
||||
#region --- 架构基类:帧处理集群 (Frame Processor Cluster) ---
|
||||
|
||||
@@ -15,6 +18,9 @@
|
||||
where TWorker : BaseWorker
|
||||
{
|
||||
#region --- 受保护成员 ---
|
||||
|
||||
private static ILogger _sysLog = Log.ForContext("SourceContext", LogModules.Core);
|
||||
|
||||
/// <summary> Worker 线程池,负责具体的帧处理任务 </summary>
|
||||
protected readonly List<TWorker> _workers = new List<TWorker>();
|
||||
|
||||
@@ -39,7 +45,10 @@
|
||||
{
|
||||
// 校验并行度参数,避免无效配置
|
||||
if (workerCount < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(workerCount), "Worker数量必须大于0");
|
||||
{
|
||||
_sysLog.Error("[Core] 帧处理集群初始化失败, 线程数必须 > 0.");
|
||||
throw new ArgumentOutOfRangeException(nameof(workerCount), "帧处理集群初始化失败, 线程数必须 > 0.");
|
||||
}
|
||||
|
||||
_configManager = configManager; // 先赋值配置管理器
|
||||
_workerCount = workerCount;
|
||||
@@ -50,7 +59,7 @@
|
||||
_workers.Add(CreateWorker(i));
|
||||
}
|
||||
|
||||
Console.WriteLine($"[{serviceName}] 服务已初始化 (并行度: {workerCount})");
|
||||
_sysLog.Information($"[Core] 帧处理集群初始化成功, {serviceName} 并行数 {workerCount}. 注: 不能大于CPU核心数.");
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -156,6 +165,8 @@
|
||||
{
|
||||
#region --- 私有成员 ---
|
||||
|
||||
private static ILogger _gRpcLog = Log.ForContext("SourceContext", LogModules.gRpc);
|
||||
|
||||
/// <summary> 线程内任务队列,容量限制100,防止内存溢出 </summary>
|
||||
private readonly BlockingCollection<(long DeviceId, SmartFrame Frame, FrameDecision Decision)> _taskQueue = new BlockingCollection<(long, SmartFrame, FrameDecision)>(100);
|
||||
|
||||
@@ -195,7 +206,7 @@
|
||||
{
|
||||
// 背压处理:丢弃当前帧,释放引用计数
|
||||
frame.Dispose();
|
||||
Console.WriteLine($"[Worker] 任务队列已满,丢弃设备 {deviceId} 的帧 (引用计数已释放)");
|
||||
_gRpcLog.Debug($"[gRpc] 任务队列已满,BaseWorker 丢弃设备 {deviceId} 的帧.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,7 +239,7 @@
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[Worker] 帧处理异常: {ex.Message}");
|
||||
_gRpcLog.Information($"[gRpc] 帧处理异常,BaseWorker 异常消息: {ex.Message}.");
|
||||
|
||||
// 异常保底策略:即使处理失败,也透传帧到下一个环节,保证流水线不中断
|
||||
NotifyFinished(taskItem.DeviceId, frame, taskItem.Decision);
|
||||
@@ -239,11 +250,11 @@
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 正常取消:线程退出,无需报错
|
||||
Console.WriteLine("[Worker] 处理循环已正常终止");
|
||||
_gRpcLog.Information($"[gRpc] BaseWorker 处理循环已正常终止.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[Worker] 处理循环异常终止: {ex.Message}");
|
||||
_gRpcLog.Error($"[gRpc] BaseWorker 处理循环异常终止.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using OpenCvSharp;
|
||||
using Ayay.SerilogLogs;
|
||||
using OpenCvSharp;
|
||||
using Serilog;
|
||||
|
||||
namespace SHH.CameraSdk
|
||||
{
|
||||
@@ -17,6 +19,9 @@ namespace SHH.CameraSdk
|
||||
public class DisplayWindowManager : IDisposable
|
||||
{
|
||||
#region --- 内部窗口上下文类 ---
|
||||
|
||||
private static ILogger _sysLog = Log.ForContext("SourceContext", LogModules.Core);
|
||||
|
||||
/// <summary>
|
||||
/// 单个窗口的上下文信息载体
|
||||
/// 存储设备关联、运行状态、回调函数等核心数据
|
||||
@@ -64,7 +69,7 @@ namespace SHH.CameraSdk
|
||||
_cameraManager = cameraManager;
|
||||
// 启动长驻UI线程,设置 LongRunning 提升调度优先级
|
||||
_uiThread = Task.Factory.StartNew(UILoop, TaskCreationOptions.LongRunning);
|
||||
Console.WriteLine("[DisplayManager] 渲染引擎就绪 (防僵尸窗口终极版)");
|
||||
_sysLog.Information("[DisplayManager] 渲染引擎就绪 (防僵尸窗口终极版)");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -77,7 +82,7 @@ namespace SHH.CameraSdk
|
||||
|
||||
// 2. 等待 UI 线程退出(最多等待1秒,防止卡死)
|
||||
try { _uiThread.Wait(1000); }
|
||||
catch (Exception ex) { Console.WriteLine($"[DisplayManager] 线程退出异常: {ex.Message}"); }
|
||||
catch (Exception ex) { _sysLog.Error($"[DisplayManager] 线程退出异常: {ex.Message}"); }
|
||||
|
||||
// 3. 强制清理所有活跃窗口
|
||||
foreach (var appId in _activeWindows.Keys)
|
||||
@@ -95,7 +100,7 @@ namespace SHH.CameraSdk
|
||||
_uiActionQueue.Dispose();
|
||||
_cts.Dispose();
|
||||
|
||||
Console.WriteLine("[DisplayManager] 渲染引擎已安全销毁");
|
||||
_sysLog.Information("[DisplayManager] 渲染引擎已安全销毁");
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -110,7 +115,7 @@ namespace SHH.CameraSdk
|
||||
// 防重入:已存在该窗口则直接返回
|
||||
if (_activeWindows.ContainsKey(appId)) return;
|
||||
|
||||
Console.WriteLine($"[DisplayManager] 正在启动窗口: {appId} -> Device {deviceId}");
|
||||
_sysLog.Information($"[DisplayManager] 正在启动窗口: {appId} -> Device {deviceId}");
|
||||
|
||||
// 初始化窗口上下文
|
||||
var context = new WindowContext
|
||||
@@ -126,7 +131,7 @@ namespace SHH.CameraSdk
|
||||
if (mouseEvent == MouseEventTypes.LButtonDown)
|
||||
{
|
||||
context.IsPaused = !context.IsPaused;
|
||||
Console.WriteLine($"[DisplayManager] 窗口 {appId} 状态切换: {(context.IsPaused ? "暂停" : "恢复")}");
|
||||
_sysLog.Information($"[DisplayManager] 窗口 {appId} 状态切换: {(context.IsPaused ? "暂停" : "恢复")}");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -148,7 +153,7 @@ namespace SHH.CameraSdk
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[DisplayManager] 窗口 {appId} 初始化失败: {ex.Message}");
|
||||
_sysLog.Error($"[DisplayManager] 窗口 {appId} 初始化失败: {ex.Message}");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -179,7 +184,7 @@ namespace SHH.CameraSdk
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[DisplayManager] 帧克隆失败: {ex.Message}");
|
||||
_sysLog.Error($"[DisplayManager] 帧克隆失败: {ex.Message}");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -223,7 +228,7 @@ namespace SHH.CameraSdk
|
||||
// 从注册表中移除窗口上下文
|
||||
if (_activeWindows.TryRemove(appId, out var context))
|
||||
{
|
||||
Console.WriteLine($"[DisplayManager] 正在清理窗口资源: {appId}");
|
||||
_sysLog.Information($"[DisplayManager] 正在清理窗口资源: {appId}");
|
||||
|
||||
// 步骤1:立即取消帧数据流订阅
|
||||
GlobalStreamDispatcher.Unsubscribe(appId);
|
||||
@@ -237,7 +242,7 @@ namespace SHH.CameraSdk
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[DisplayManager] 窗口 {appId} 销毁失败: {ex.Message}");
|
||||
_sysLog.Error($"[DisplayManager] 窗口 {appId} 销毁失败: {ex.Message}");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -257,7 +262,7 @@ namespace SHH.CameraSdk
|
||||
if (_activeWindows.TryGetValue(appId, out var ctx))
|
||||
{
|
||||
ctx.IsPaused = true;
|
||||
Console.WriteLine($"[DisplayManager] 窗口 {appId} 已暂停");
|
||||
_sysLog.Information($"[DisplayManager] 窗口 {appId} 已暂停");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +275,7 @@ namespace SHH.CameraSdk
|
||||
if (_activeWindows.TryGetValue(appId, out var ctx))
|
||||
{
|
||||
ctx.IsPaused = false;
|
||||
Console.WriteLine($"[DisplayManager] 窗口 {appId} 已恢复");
|
||||
_sysLog.Information($"[DisplayManager] 窗口 {appId} 已恢复");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
@@ -290,7 +295,7 @@ namespace SHH.CameraSdk
|
||||
var device = _cameraManager.GetDevice(deviceId);
|
||||
if (device == null)
|
||||
{
|
||||
Console.WriteLine($"[策略联动] 设备 {deviceId} 不存在");
|
||||
_sysLog.Information($"[策略联动] 设备 {deviceId} 不存在");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -298,7 +303,7 @@ namespace SHH.CameraSdk
|
||||
var frameController = device.Controller;
|
||||
if (frameController == null)
|
||||
{
|
||||
Console.WriteLine($"[策略联动] 设备 {deviceId} 未配置帧控制器");
|
||||
_sysLog.Information($"[策略联动] 设备 {deviceId} 未配置帧控制器");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -306,12 +311,12 @@ namespace SHH.CameraSdk
|
||||
if (fps > 0)
|
||||
{
|
||||
frameController.Register(appId, fps);
|
||||
Console.WriteLine($"[策略联动] ✅ 已注册流控: {appId} -> {fps} FPS");
|
||||
_sysLog.Information($"[策略联动] ✅ 已注册流控: {appId} -> {fps} FPS");
|
||||
}
|
||||
else
|
||||
{
|
||||
frameController.Unregister(appId);
|
||||
Console.WriteLine($"[策略联动] 🗑️ 已注销流控: {appId}");
|
||||
_sysLog.Information($"[策略联动] 🗑️ 已注销流控: {appId}");
|
||||
}
|
||||
|
||||
// 记录审计日志,用于前端排查问题
|
||||
@@ -319,7 +324,7 @@ namespace SHH.CameraSdk
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[策略联动] ❌ 联动失败: {ex.Message}");
|
||||
_sysLog.Error($"[策略联动] ❌ 联动失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
@@ -360,7 +365,7 @@ namespace SHH.CameraSdk
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[UI] 渲染循环异常: {ex.Message}");
|
||||
_sysLog.Error($"[UI] 渲染循环异常: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,7 +390,7 @@ namespace SHH.CameraSdk
|
||||
// Visible < 1.0 表示窗口已被用户手动关闭
|
||||
if (Cv2.GetWindowProperty(appId, WindowPropertyFlags.Visible) < 1.0)
|
||||
{
|
||||
Console.WriteLine($"[UI] 检测到窗口 {appId} 已被手动关闭,触发清理...");
|
||||
_sysLog.Information($"[UI] 检测到窗口 {appId} 已被手动关闭,触发清理...");
|
||||
// 异步清理:避免 StopDisplay 内部的队列操作阻塞 UI 线程
|
||||
Task.Run(() => StopDisplay(appId));
|
||||
}
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
public class FileStorageService : IStorageService
|
||||
{
|
||||
public int ProcessId { get; }
|
||||
private readonly string _baseDir;
|
||||
private readonly string _devicesPath;
|
||||
private readonly string _systemLogPath; // 系统日志路径
|
||||
private readonly string _logsDir; // 设备日志文件夹
|
||||
|
||||
// 【关键优化】双锁分离:配置读写和日志读写互不干扰
|
||||
private readonly SemaphoreSlim _configLock = new SemaphoreSlim(1, 1);
|
||||
private readonly SemaphoreSlim _logLock = new SemaphoreSlim(1, 1);
|
||||
|
||||
// JSON 配置 (保持不变)
|
||||
private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
IncludeFields = true,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
NumberHandling = JsonNumberHandling.AllowReadingFromString
|
||||
};
|
||||
|
||||
public FileStorageService(int processId)
|
||||
{
|
||||
ProcessId = processId;
|
||||
|
||||
// 目录结构:
|
||||
// App_Data/Process_1/
|
||||
// ├── devices.json
|
||||
// ├── system.log
|
||||
// └── logs/
|
||||
// ├── device_101.log
|
||||
// └── device_102.log
|
||||
|
||||
_baseDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "App_Data", $"Process_{processId}");
|
||||
_devicesPath = Path.Combine(_baseDir, "devices.json");
|
||||
_systemLogPath = Path.Combine(_baseDir, "system.log");
|
||||
_logsDir = Path.Combine(_baseDir, "logs");
|
||||
|
||||
if (!Directory.Exists(_baseDir)) Directory.CreateDirectory(_baseDir);
|
||||
if (!Directory.Exists(_logsDir)) Directory.CreateDirectory(_logsDir);
|
||||
|
||||
Console.WriteLine($"[Storage] 服务就绪 | 日志路径: {_systemLogPath}");
|
||||
}
|
||||
|
||||
// ==================================================================
|
||||
// 1. 设备配置管理 (使用 _configLock)
|
||||
// ==================================================================
|
||||
|
||||
public async Task SaveDevicesAsync(IEnumerable<VideoSourceConfig> configs)
|
||||
{
|
||||
await _configLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (SdkGlobal.SaveCameraConfigEnable)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(configs, _jsonOptions);
|
||||
await File.WriteAllTextAsync(_devicesPath, json);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[Storage] ❌ 保存配置失败: {ex.Message}");
|
||||
}
|
||||
finally { _configLock.Release(); }
|
||||
}
|
||||
|
||||
public async Task<List<VideoSourceConfig>> LoadDevicesAsync()
|
||||
{
|
||||
if (!File.Exists(_devicesPath)) return new List<VideoSourceConfig>();
|
||||
|
||||
await _configLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (!SdkGlobal.SaveCameraConfigEnable)
|
||||
return new List<VideoSourceConfig>();
|
||||
|
||||
var json = await File.ReadAllTextAsync(_devicesPath);
|
||||
if (string.IsNullOrWhiteSpace(json)) return new List<VideoSourceConfig>();
|
||||
|
||||
var list = JsonSerializer.Deserialize<List<VideoSourceConfig>>(json, _jsonOptions);
|
||||
return list ?? new List<VideoSourceConfig>();
|
||||
//return new List<VideoSourceConfig>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[Storage] ❌ 读取配置失败: {ex.Message}");
|
||||
return new List<VideoSourceConfig>();
|
||||
}
|
||||
finally { _configLock.Release(); }
|
||||
}
|
||||
|
||||
// ==================================================================
|
||||
// 2. 系统操作日志 (使用 _logLock)
|
||||
// ==================================================================
|
||||
|
||||
public async Task AppendSystemLogAsync(string action, string ip, string path)
|
||||
{
|
||||
// 格式: [时间] | IP | 动作 路径
|
||||
var time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
var line = $"[{time}] | {ip} | {action} {path}";
|
||||
|
||||
await _logLock.WaitAsync(); // 等待日志锁
|
||||
try
|
||||
{
|
||||
// 追加写入 (Async)
|
||||
await File.AppendAllTextAsync(_systemLogPath, line + Environment.NewLine);
|
||||
}
|
||||
catch { /* 忽略日志写入错误,别崩了主程序 */ }
|
||||
finally { _logLock.Release(); }
|
||||
}
|
||||
|
||||
public async Task<List<string>> GetSystemLogsAsync(int count)
|
||||
{
|
||||
if (!File.Exists(_systemLogPath)) return new List<string> { "暂无日志" };
|
||||
|
||||
await _logLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
// 读取所有行 (如果日志文件非常大,这里建议优化为倒序读取,但几MB以内没问题)
|
||||
var lines = await File.ReadAllLinesAsync(_systemLogPath);
|
||||
|
||||
// 取最后 N 行,并反转(让最新的显示在最上面)
|
||||
return lines.TakeLast(count).Reverse().ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new List<string> { $"读取失败: {ex.Message}" };
|
||||
}
|
||||
finally { _logLock.Release(); }
|
||||
}
|
||||
|
||||
// ==================================================================
|
||||
// 3. 设备审计日志 (使用 _logLock)
|
||||
// ==================================================================
|
||||
|
||||
public async Task AppendDeviceLogAsync(int deviceId, string message)
|
||||
{
|
||||
var path = Path.Combine(_logsDir, $"device_{deviceId}.log");
|
||||
var time = DateTime.Now.ToString("MM-dd HH:mm:ss");
|
||||
var line = $"{time} > {message}";
|
||||
|
||||
await _logLock.WaitAsync(); // 复用日志锁,防止多文件同时IO导致磁盘抖动
|
||||
try
|
||||
{
|
||||
await File.AppendAllTextAsync(path, line + Environment.NewLine);
|
||||
}
|
||||
catch { }
|
||||
finally { _logLock.Release(); }
|
||||
}
|
||||
|
||||
public async Task<List<string>> GetDeviceLogsAsync(int deviceId, int count)
|
||||
{
|
||||
var path = Path.Combine(_logsDir, $"device_{deviceId}.log");
|
||||
if (!File.Exists(path)) return new List<string>();
|
||||
|
||||
await _logLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
var lines = await File.ReadAllLinesAsync(path);
|
||||
return lines.TakeLast(count).Reverse().ToList();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
finally { _logLock.Release(); }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
namespace SHH.CameraSdk;
|
||||
using Ayay.SerilogLogs;
|
||||
using Serilog;
|
||||
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
/// <summary>
|
||||
/// [配置中心] 预处理参数管理器
|
||||
@@ -6,6 +9,8 @@
|
||||
/// </summary>
|
||||
public class ProcessingConfigManager
|
||||
{
|
||||
private static ILogger _sysLog = Log.ForContext("SourceContext", LogModules.Core);
|
||||
|
||||
// 内存字典:Key=设备ID, Value=配置对象
|
||||
private readonly ConcurrentDictionary<long, ProcessingOptions> _configs = new();
|
||||
|
||||
@@ -30,7 +35,7 @@ public class ProcessingConfigManager
|
||||
// 直接覆盖旧配置,由于是引用替换,原子性较高
|
||||
_configs.AddOrUpdate(deviceId, newOptions, (key, old) => newOptions);
|
||||
|
||||
Console.WriteLine($"[ConfigManager] 设备 {deviceId} 预处理参数已更新: " +
|
||||
_sysLog.Information($"[ConfigManager] 设备 {deviceId} 预处理参数已更新: " +
|
||||
$"Expand={newOptions.EnableExpand} Shrink:{newOptions.EnableShrink} 分辨率:({newOptions.TargetWidth}x{newOptions.TargetHeight}), " +
|
||||
$"EnableBrightness}}={newOptions.EnableBrightness}");
|
||||
}
|
||||
|
||||
@@ -9,13 +9,10 @@ namespace SHH.CameraSdk;
|
||||
/// </summary>
|
||||
public class UserActionFilter : IActionFilter
|
||||
{
|
||||
private readonly IStorageService _storage;
|
||||
|
||||
// 【关键点】构造函数注入
|
||||
// ASP.NET Core 会自动把我们在 Program.cs 中注册的 IStorageService 实例传进来
|
||||
public UserActionFilter(IStorageService storage)
|
||||
public UserActionFilter()
|
||||
{
|
||||
_storage = storage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -32,11 +29,6 @@ public class UserActionFilter : IActionFilter
|
||||
if (method != "GET")
|
||||
{
|
||||
var ip = context.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "Unknown";
|
||||
|
||||
// 3. 调用存储服务写入日志
|
||||
// 注意:这里我们不等待任务完成 (Fire-and-Forget),以免日志写入拖慢 API 响应速度
|
||||
// 因为 _storage.AppendSystemLogAsync 内部目前是空实现(Task.CompletedTask),所以这里绝对不会卡顿
|
||||
_ = _storage.AppendSystemLogAsync(method, ip, path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user