用了统一存储,之前显示系统日志的读方法需要修改

This commit is contained in:
2025-12-26 23:05:08 +08:00
parent 3d4eb34ca9
commit 39404c0acd
3 changed files with 188 additions and 146 deletions

View File

@@ -140,7 +140,7 @@ public class CameraManager : IDisposable, IAsyncDisposable
{
var device = CreateDeviceInstance(config);
// 默认设为运行状态,让协调器稍后去连接
device.IsRunning = true;
//device.IsRunning = true;
_cameraPool.TryAdd(config.Id, device);
loadedCount++;
}

View File

@@ -1,105 +1,165 @@
using System.Text.Json;
namespace SHH.CameraSdk
namespace SHH.CameraSdk;
public class FileStorageService : IStorageService
{
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
{
public int ProcessId { get; }
private readonly string _baseDir;
private readonly string _devicesPath;
private readonly SemaphoreSlim _fileLock = new SemaphoreSlim(1, 1);
WriteIndented = true,
IncludeFields = true,
PropertyNameCaseInsensitive = true,
NumberHandling = JsonNumberHandling.AllowReadingFromString
};
// [关键修复] 配置序列化选项,解决“只存属性不存字段”的问题
private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions
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
{
WriteIndented = true, // 格式化 JSON让人眼可读
IncludeFields = true, // [核心] 允许序列化 public int Id; 这种字段
PropertyNameCaseInsensitive = true, // 忽略大小写差异
NumberHandling = JsonNumberHandling.AllowReadingFromString // 允许 "8000" 读为 int 8000
};
public FileStorageService(int processId)
{
ProcessId = processId;
_baseDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "App_Data", $"Process_{processId}");
_devicesPath = Path.Combine(_baseDir, "devices.json");
if (!Directory.Exists(_baseDir)) Directory.CreateDirectory(_baseDir);
Console.WriteLine($"[Storage] 路径: {_devicesPath}");
var json = JsonSerializer.Serialize(configs, _jsonOptions);
await File.WriteAllTextAsync(_devicesPath, json);
}
public async Task SaveDevicesAsync(IEnumerable<VideoSourceConfig> configs)
catch (Exception ex)
{
await _fileLock.WaitAsync();
try
{
// [调试] 打印正在保存的数量,确保 Manager 传过来的数据是对的
// Console.WriteLine($"[Debug] 正在保存 {configs.Count()} 台设备...");
var json = JsonSerializer.Serialize(configs, _jsonOptions);
await File.WriteAllTextAsync(_devicesPath, json);
// [调试] 打印部分 JSON 内容,验证是否为空对象 "{}"
// if (json.Length < 200) Console.WriteLine($"[Debug] JSON 内容: {json}");
}
catch (Exception ex)
{
Console.WriteLine($"[Storage] ❌ 保存配置失败: {ex.Message}");
}
finally
{
_fileLock.Release();
}
Console.WriteLine($"[Storage] ❌ 保存配置失败: {ex.Message}");
}
finally { _configLock.Release(); }
}
public async Task<List<VideoSourceConfig>> LoadDevicesAsync()
public async Task<List<VideoSourceConfig>> LoadDevicesAsync()
{
if (!File.Exists(_devicesPath)) return new List<VideoSourceConfig>();
await _configLock.WaitAsync();
try
{
if (!File.Exists(_devicesPath))
{
Console.WriteLine("[Storage] ⚠️ 配置文件不存在,将使用空列表");
return new List<VideoSourceConfig>();
}
var json = await File.ReadAllTextAsync(_devicesPath);
if (string.IsNullOrWhiteSpace(json)) return new List<VideoSourceConfig>();
await _fileLock.WaitAsync();
try
{
var json = await File.ReadAllTextAsync(_devicesPath);
if (string.IsNullOrWhiteSpace(json)) return new List<VideoSourceConfig>();
// [调试] 打印读取到的原始 JSON
// Console.WriteLine($"[Debug] 读取文件内容: {json.Substring(0, Math.Min(json.Length, 100))}...");
var list = JsonSerializer.Deserialize<List<VideoSourceConfig>>(json, _jsonOptions);
// 二次校验:如果读出来列表不为空,但 ID 全是 0说明序列化还是没对上
if (list != null && list.Count > 0 && list[0].Id == 0 && list[0].Port == 0)
{
Console.WriteLine("[Storage] ⚠️ 警告:读取到设备,但字段似乎为空。请检查 VideoSourceConfig 是否使用了 private 属性?");
}
return list ?? new List<VideoSourceConfig>();
}
catch (Exception ex)
{
Console.WriteLine($"[Storage] ❌ 读取配置失败: {ex.Message}");
// 出错时返回空列表,不要抛出异常,否则 StartAsync 会崩溃
return new List<VideoSourceConfig>();
}
finally
{
_fileLock.Release();
}
var list = JsonSerializer.Deserialize<List<VideoSourceConfig>>(json, _jsonOptions);
return list ?? new List<VideoSourceConfig>();
}
catch (Exception ex)
{
Console.WriteLine($"[Storage] ❌ 读取配置失败: {ex.Message}");
return new List<VideoSourceConfig>();
}
finally { _configLock.Release(); }
}
// ==================================================================
// 日志部分 (保持空实现以免干扰)
// ==================================================================
public Task AppendSystemLogAsync(string action, string ip, string path) => Task.CompletedTask;
public Task<List<string>> GetSystemLogsAsync(int count) => Task.FromResult(new List<string>());
public Task AppendDeviceLogAsync(int deviceId, string message) => Task.CompletedTask;
public Task<List<string>> GetDeviceLogsAsync(int deviceId, int count) => Task.FromResult(new List<string>());
// ==================================================================
// 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(); }
}
}