165 lines
5.8 KiB
C#
165 lines
5.8 KiB
C#
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
|
||
{
|
||
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
|
||
{
|
||
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>();
|
||
}
|
||
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(); }
|
||
}
|
||
} |