using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using WIDESEAWCS_S7Simulator.Core.Entities;
using WIDESEAWCS_S7Simulator.Core.Enums;
using WIDESEAWCS_S7Simulator.Core.Interfaces;
using WIDESEAWCS_S7Simulator.Core.Persistence.Models;
namespace WIDESEAWCS_S7Simulator.Core.Persistence
{
///
/// 文件持久化服务实现
/// 将实例配置和内存数据保存到本地JSON文件
///
public class FilePersistenceService : IPersistenceService
{
///
/// 数据目录路径
///
private readonly string _dataPath;
///
/// JSON序列化选项(线程安全)
///
private static readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
///
/// 文件操作锁(线程安全)
///
private readonly SemaphoreSlim _fileLock = new SemaphoreSlim(1, 1);
///
/// 构造函数
///
/// 数据目录路径
public FilePersistenceService(string dataPath = "Data")
{
// 转换为绝对路径(基于当前工作目录)
_dataPath = Path.GetFullPath(dataPath);
try
{
// 确保数据目录存在
if (!Directory.Exists(_dataPath))
{
Directory.CreateDirectory(_dataPath);
}
}
catch (Exception ex)
{
Console.WriteLine($"Error creating data directory '{_dataPath}': {ex.Message}");
throw;
}
}
///
/// 保存实例配置
///
public async Task SaveInstanceConfigAsync(InstanceConfig config)
{
if (config == null)
throw new ArgumentNullException(nameof(config));
await _fileLock.WaitAsync();
try
{
var instanceDir = GetInstanceDirectory(config.Id);
if (!Directory.Exists(instanceDir))
{
Directory.CreateDirectory(instanceDir);
}
var configPath = Path.Combine(instanceDir, "config.json");
var model = ToDataModel(config);
var json = JsonSerializer.Serialize(model, _jsonOptions);
await WriteFileAtomicAsync(configPath, json);
}
catch (Exception ex)
{
Console.WriteLine($"Error saving instance config for '{config.Id}': {ex.Message}");
throw;
}
finally
{
_fileLock.Release();
}
}
///
/// 加载实例配置
///
public async Task LoadInstanceConfigAsync(string instanceId)
{
if (string.IsNullOrWhiteSpace(instanceId))
throw new ArgumentException("实例ID不能为空", nameof(instanceId));
ValidateInstanceId(instanceId);
await _fileLock.WaitAsync();
try
{
var configPath = Path.Combine(GetInstanceDirectory(instanceId), "config.json");
if (!File.Exists(configPath))
throw new FileNotFoundException($"实例配置文件不存在: {configPath}");
var json = await File.ReadAllTextAsync(configPath);
var model = JsonSerializer.Deserialize(json, _jsonOptions);
if (model == null)
throw new InvalidOperationException("无法反序列化实例配置");
return ToEntity(model);
}
catch (Exception ex) when (!(ex is FileNotFoundException || ex is InvalidOperationException || ex is ArgumentException))
{
Console.WriteLine($"Error loading instance config for '{instanceId}': {ex.Message}");
throw;
}
finally
{
_fileLock.Release();
}
}
///
/// 加载所有实例配置
///
public async Task> LoadAllInstanceConfigsAsync()
{
await _fileLock.WaitAsync();
try
{
var configs = new List();
if (!Directory.Exists(_dataPath))
return configs;
var instanceDirs = Directory.GetDirectories(_dataPath)
.Where(d => Path.GetFileName(d).StartsWith("instance-"))
.ToList();
foreach (var dir in instanceDirs)
{
var configPath = Path.Combine(dir, "config.json");
if (File.Exists(configPath))
{
try
{
var json = await File.ReadAllTextAsync(configPath);
var model = JsonSerializer.Deserialize(json, _jsonOptions);
if (model != null)
{
configs.Add(ToEntity(model));
}
}
catch (Exception ex)
{
Console.WriteLine($"Error loading instance config from '{configPath}': {ex.Message}");
// 跳过无法加载的配置文件
continue;
}
}
}
return configs;
}
finally
{
_fileLock.Release();
}
}
///
/// 删除实例配置
///
public async Task DeleteInstanceConfigAsync(string instanceId)
{
if (string.IsNullOrWhiteSpace(instanceId))
throw new ArgumentException("实例ID不能为空", nameof(instanceId));
ValidateInstanceId(instanceId);
await _fileLock.WaitAsync();
try
{
var instanceDir = GetInstanceDirectory(instanceId);
if (Directory.Exists(instanceDir))
{
Directory.Delete(instanceDir, recursive: true);
}
}
catch (Exception ex)
{
Console.WriteLine($"Error deleting instance '{instanceId}': {ex.Message}");
throw;
}
finally
{
_fileLock.Release();
}
}
///
/// 保存内存数据
///
public async Task SaveMemoryDataAsync(string instanceId, IMemoryStore memoryStore)
{
if (string.IsNullOrWhiteSpace(instanceId))
throw new ArgumentException("实例ID不能为空", nameof(instanceId));
ValidateInstanceId(instanceId);
if (memoryStore == null)
throw new ArgumentNullException(nameof(memoryStore));
await _fileLock.WaitAsync();
try
{
var instanceDir = GetInstanceDirectory(instanceId);
if (!Directory.Exists(instanceDir))
{
Directory.CreateDirectory(instanceDir);
}
var memoryPath = Path.Combine(instanceDir, "memory.json");
var exportedData = memoryStore.Export();
// 将字节数组转换为Base64字符串以便JSON序列化
var memoryDataModel = new MemoryDataModel
{
MemoryData = exportedData.ToDictionary(
kvp => kvp.Key,
kvp => Convert.ToBase64String(kvp.Value)
)
};
var json = JsonSerializer.Serialize(memoryDataModel, _jsonOptions);
await WriteFileAtomicAsync(memoryPath, json);
}
catch (Exception ex)
{
Console.WriteLine($"Error saving memory data for instance '{instanceId}': {ex.Message}");
throw;
}
finally
{
_fileLock.Release();
}
}
///
/// 加载内存数据
///
public async Task LoadMemoryDataAsync(string instanceId, IMemoryStore memoryStore)
{
if (string.IsNullOrWhiteSpace(instanceId))
throw new ArgumentException("实例ID不能为空", nameof(instanceId));
ValidateInstanceId(instanceId);
if (memoryStore == null)
throw new ArgumentNullException(nameof(memoryStore));
await _fileLock.WaitAsync();
try
{
var memoryPath = Path.Combine(GetInstanceDirectory(instanceId), "memory.json");
if (!File.Exists(memoryPath))
return; // 内存文件不存在,跳过加载
var json = await File.ReadAllTextAsync(memoryPath);
var memoryDataModel = JsonSerializer.Deserialize(json, _jsonOptions);
if (memoryDataModel?.MemoryData == null)
return;
// 将Base64字符串转换回字节数组
var importedData = new Dictionary();
foreach (var kvp in memoryDataModel.MemoryData)
{
try
{
importedData[kvp.Key] = Convert.FromBase64String(kvp.Value);
}
catch (FormatException ex)
{
Console.WriteLine($"Warning: Invalid Base64 data for memory region '{kvp.Key}' in instance '{instanceId}': {ex.Message}");
// 跳过无效的Base64数据
continue;
}
}
memoryStore.Import(importedData);
}
catch (Exception ex) when (!(ex is FileNotFoundException))
{
Console.WriteLine($"Error loading memory data for instance '{instanceId}': {ex.Message}");
throw;
}
finally
{
_fileLock.Release();
}
}
///
/// 获取实例目录路径
///
private string GetInstanceDirectory(string instanceId)
{
return Path.Combine(_dataPath, $"instance-{instanceId}");
}
///
/// 验证实例ID(防止路径遍历攻击)
///
private void ValidateInstanceId(string instanceId)
{
if (string.IsNullOrWhiteSpace(instanceId))
throw new ArgumentException("实例ID不能为空", nameof(instanceId));
// 检查路径遍历字符
if (instanceId.Contains("..") || instanceId.Contains("/") || instanceId.Contains("\\"))
throw new ArgumentException("实例ID包含非法字符", nameof(instanceId));
// 检查无效路径字符
var invalidChars = Path.GetInvalidFileNameChars();
if (instanceId.IndexOfAny(invalidChars) >= 0)
throw new ArgumentException("实例ID包含非法字符", nameof(instanceId));
}
///
/// 写入文件(直接写入,简化版本)
///
private async Task WriteFileAtomicAsync(string filePath, string content)
{
// 确保目标文件的父目录存在
var directory = Path.GetDirectoryName(filePath);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
// 直接写入文件
await File.WriteAllTextAsync(filePath, content, Encoding.UTF8);
}
///
/// 将实体转换为数据模型
///
private InstanceDataModel ToDataModel(InstanceConfig config)
{
return new InstanceDataModel
{
Id = config.Id,
Name = config.Name,
PlcType = config.PLCType.ToString(),
Port = config.Port,
ActivationKey = config.ActivationKey,
AutoStart = config.AutoStart,
MemoryConfig = new MemoryRegionConfigModel
{
MRegionSize = config.MemoryConfig.MRegionSize,
DBBlockCount = config.MemoryConfig.DBBlockCount,
DBBlockSize = config.MemoryConfig.DBBlockSize,
IRegionSize = config.MemoryConfig.IRegionSize,
QRegionSize = config.MemoryConfig.QRegionSize,
TRegionCount = config.MemoryConfig.TRegionCount,
CRegionCount = config.MemoryConfig.CRegionCount
}
};
}
///
/// 将数据模型转换为实体
///
private InstanceConfig ToEntity(InstanceDataModel model)
{
return new InstanceConfig
{
Id = model.Id,
Name = model.Name,
PLCType = Enum.Parse(model.PlcType),
Port = model.Port,
ActivationKey = model.ActivationKey,
AutoStart = model.AutoStart,
MemoryConfig = new MemoryRegionConfig
{
MRegionSize = model.MemoryConfig.MRegionSize,
DBBlockCount = model.MemoryConfig.DBBlockCount,
DBBlockSize = model.MemoryConfig.DBBlockSize,
IRegionSize = model.MemoryConfig.IRegionSize,
QRegionSize = model.MemoryConfig.QRegionSize,
TRegionCount = model.MemoryConfig.TRegionCount,
CRegionCount = model.MemoryConfig.CRegionCount
}
};
}
}
}