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 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> LoadDevicesAsync() { if (!File.Exists(_devicesPath)) return new List(); await _configLock.WaitAsync(); try { var json = await File.ReadAllTextAsync(_devicesPath); if (string.IsNullOrWhiteSpace(json)) return new List(); var list = JsonSerializer.Deserialize>(json, _jsonOptions); return list ?? new List(); //return new List(); } catch (Exception ex) { Console.WriteLine($"[Storage] ❌ 读取配置失败: {ex.Message}"); return new List(); } 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> GetSystemLogsAsync(int count) { if (!File.Exists(_systemLogPath)) return new List { "暂无日志" }; await _logLock.WaitAsync(); try { // 读取所有行 (如果日志文件非常大,这里建议优化为倒序读取,但几MB以内没问题) var lines = await File.ReadAllLinesAsync(_systemLogPath); // 取最后 N 行,并反转(让最新的显示在最上面) return lines.TakeLast(count).Reverse().ToList(); } catch (Exception ex) { return new List { $"读取失败: {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> GetDeviceLogsAsync(int deviceId, int count) { var path = Path.Combine(_logsDir, $"device_{deviceId}.log"); if (!File.Exists(path)) return new List(); await _logLock.WaitAsync(); try { var lines = await File.ReadAllLinesAsync(path); return lines.TakeLast(count).Reverse().ToList(); } catch { return new List(); } finally { _logLock.Release(); } } }