wanshenmean
2026-03-13 efbfb630f60f6663deba83ec12c1fbf2f9bab183
Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Core/Persistence/FilePersistenceService.cs
@@ -2,7 +2,9 @@
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;
@@ -23,9 +25,18 @@
        private readonly string _dataPath;
        /// <summary>
        /// JSON序列化选项
        /// JSON序列化选项(线程安全)
        /// </summary>
        private readonly JsonSerializerOptions _jsonOptions;
        private static readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions
        {
            WriteIndented = true,
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase
        };
        /// <summary>
        /// 文件操作锁(线程安全)
        /// </summary>
        private readonly SemaphoreSlim _fileLock = new SemaphoreSlim(1, 1);
        /// <summary>
        /// 构造函数
@@ -34,16 +45,19 @@
        public FilePersistenceService(string dataPath = "Data")
        {
            _dataPath = dataPath;
            _jsonOptions = new JsonSerializerOptions
            {
                WriteIndented = true,
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase
            };
            // 确保数据目录存在
            if (!Directory.Exists(_dataPath))
            try
            {
                Directory.CreateDirectory(_dataPath);
                // 确保数据目录存在
                if (!Directory.Exists(_dataPath))
                {
                    Directory.CreateDirectory(_dataPath);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error creating data directory '{_dataPath}': {ex.Message}");
                throw;
            }
        }
@@ -55,17 +69,30 @@
            if (config == null)
                throw new ArgumentNullException(nameof(config));
            var instanceDir = GetInstanceDirectory(config.Id);
            if (!Directory.Exists(instanceDir))
            await _fileLock.WaitAsync();
            try
            {
                Directory.CreateDirectory(instanceDir);
                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);
            }
            var configPath = Path.Combine(instanceDir, "config.json");
            var model = ToDataModel(config);
            var json = JsonSerializer.Serialize(model, _jsonOptions);
            await File.WriteAllTextAsync(configPath, json);
            catch (Exception ex)
            {
                Console.WriteLine($"Error saving instance config for '{config.Id}': {ex.Message}");
                throw;
            }
            finally
            {
                _fileLock.Release();
            }
        }
        /// <summary>
@@ -76,17 +103,32 @@
            if (string.IsNullOrWhiteSpace(instanceId))
                throw new ArgumentException("实例ID不能为空", nameof(instanceId));
            var configPath = Path.Combine(GetInstanceDirectory(instanceId), "config.json");
            if (!File.Exists(configPath))
                throw new FileNotFoundException($"实例配置文件不存在: {configPath}");
            ValidateInstanceId(instanceId);
            var json = await File.ReadAllTextAsync(configPath);
            var model = JsonSerializer.Deserialize<InstanceDataModel>(json, _jsonOptions);
            await _fileLock.WaitAsync();
            try
            {
                var configPath = Path.Combine(GetInstanceDirectory(instanceId), "config.json");
                if (!File.Exists(configPath))
                    throw new FileNotFoundException($"实例配置文件不存在: {configPath}");
            if (model == null)
                throw new InvalidOperationException("无法反序列化实例配置");
                var json = await File.ReadAllTextAsync(configPath);
                var model = JsonSerializer.Deserialize<InstanceDataModel>(json, _jsonOptions);
            return ToEntity(model);
                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();
            }
        }
        /// <summary>
@@ -94,55 +136,77 @@
        /// </summary>
        public async Task<List<InstanceConfig>> LoadAllInstanceConfigsAsync()
        {
            var configs = new List<InstanceConfig>();
            if (!Directory.Exists(_dataPath))
                return configs;
            var instanceDirs = Directory.GetDirectories(_dataPath)
                .Where(d => Path.GetFileName(d).StartsWith("instance-"))
                .ToList();
            foreach (var dir in instanceDirs)
            await _fileLock.WaitAsync();
            try
            {
                var configPath = Path.Combine(dir, "config.json");
                if (File.Exists(configPath))
                var configs = new List<InstanceConfig>();
                if (!Directory.Exists(_dataPath))
                    return configs;
                var instanceDirs = Directory.GetDirectories(_dataPath)
                    .Where(d => Path.GetFileName(d).StartsWith("instance-"))
                    .ToList();
                foreach (var dir in instanceDirs)
                {
                    try
                    var configPath = Path.Combine(dir, "config.json");
                    if (File.Exists(configPath))
                    {
                        var json = await File.ReadAllTextAsync(configPath);
                        var model = JsonSerializer.Deserialize<InstanceDataModel>(json, _jsonOptions);
                        if (model != null)
                        try
                        {
                            configs.Add(ToEntity(model));
                            var json = await File.ReadAllTextAsync(configPath);
                            var model = JsonSerializer.Deserialize<InstanceDataModel>(json, _jsonOptions);
                            if (model != null)
                            {
                                configs.Add(ToEntity(model));
                            }
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine($"Error loading instance config from '{configPath}': {ex.Message}");
                            // 跳过无法加载的配置文件
                            continue;
                        }
                    }
                    catch (Exception)
                    {
                        // 跳过无法加载的配置文件
                        continue;
                    }
                }
            }
            return configs;
                return configs;
            }
            finally
            {
                _fileLock.Release();
            }
        }
        /// <summary>
        /// 删除实例配置
        /// </summary>
        public Task DeleteInstanceConfigAsync(string instanceId)
        public async Task DeleteInstanceConfigAsync(string instanceId)
        {
            if (string.IsNullOrWhiteSpace(instanceId))
                throw new ArgumentException("实例ID不能为空", nameof(instanceId));
            var instanceDir = GetInstanceDirectory(instanceId);
            if (Directory.Exists(instanceDir))
            {
                Directory.Delete(instanceDir, recursive: true);
            }
            ValidateInstanceId(instanceId);
            return Task.CompletedTask;
            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();
            }
        }
        /// <summary>
@@ -153,29 +217,44 @@
            if (string.IsNullOrWhiteSpace(instanceId))
                throw new ArgumentException("实例ID不能为空", nameof(instanceId));
            ValidateInstanceId(instanceId);
            if (memoryStore == null)
                throw new ArgumentNullException(nameof(memoryStore));
            var instanceDir = GetInstanceDirectory(instanceId);
            if (!Directory.Exists(instanceDir))
            await _fileLock.WaitAsync();
            try
            {
                Directory.CreateDirectory(instanceDir);
                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);
            }
            var memoryPath = Path.Combine(instanceDir, "memory.json");
            var exportedData = memoryStore.Export();
            // 将字节数组转换为Base64字符串以便JSON序列化
            var memoryDataModel = new MemoryDataModel
            catch (Exception ex)
            {
                MemoryData = exportedData.ToDictionary(
                    kvp => kvp.Key,
                    kvp => Convert.ToBase64String(kvp.Value)
                )
            };
            var json = JsonSerializer.Serialize(memoryDataModel, _jsonOptions);
            await File.WriteAllTextAsync(memoryPath, json);
                Console.WriteLine($"Error saving memory data for instance '{instanceId}': {ex.Message}");
                throw;
            }
            finally
            {
                _fileLock.Release();
            }
        }
        /// <summary>
@@ -186,35 +265,51 @@
            if (string.IsNullOrWhiteSpace(instanceId))
                throw new ArgumentException("实例ID不能为空", nameof(instanceId));
            ValidateInstanceId(instanceId);
            if (memoryStore == null)
                throw new ArgumentNullException(nameof(memoryStore));
            var memoryPath = Path.Combine(GetInstanceDirectory(instanceId), "memory.json");
            if (!File.Exists(memoryPath))
                return; // 内存文件不存在,跳过加载
            var json = await File.ReadAllTextAsync(memoryPath);
            var memoryDataModel = JsonSerializer.Deserialize<MemoryDataModel>(json, _jsonOptions);
            if (memoryDataModel?.MemoryData == null)
                return;
            // 将Base64字符串转换回字节数组
            var importedData = new Dictionary<string, byte[]>();
            foreach (var kvp in memoryDataModel.MemoryData)
            await _fileLock.WaitAsync();
            try
            {
                try
                {
                    importedData[kvp.Key] = Convert.FromBase64String(kvp.Value);
                }
                catch (FormatException)
                {
                    // 跳过无效的Base64数据
                    continue;
                }
            }
                var memoryPath = Path.Combine(GetInstanceDirectory(instanceId), "memory.json");
                if (!File.Exists(memoryPath))
                    return; // 内存文件不存在,跳过加载
            memoryStore.Import(importedData);
                var json = await File.ReadAllTextAsync(memoryPath);
                var memoryDataModel = JsonSerializer.Deserialize<MemoryDataModel>(json, _jsonOptions);
                if (memoryDataModel?.MemoryData == null)
                    return;
                // 将Base64字符串转换回字节数组
                var importedData = new Dictionary<string, byte[]>();
                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();
            }
        }
        /// <summary>
@@ -226,6 +321,57 @@
        }
        /// <summary>
        /// 验证实例ID(防止路径遍历攻击)
        /// </summary>
        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));
        }
        /// <summary>
        /// 原子性写入文件(使用临时文件+替换模式)
        /// </summary>
        private async Task WriteFileAtomicAsync(string filePath, string content)
        {
            var tempPath = filePath + ".tmp";
            try
            {
                // 写入临时文件
                await File.WriteAllTextAsync(tempPath, content, Encoding.UTF8);
                // 原子性替换目标文件
                File.Replace(tempPath, filePath, null);
            }
            catch
            {
                // 清理临时文件
                if (File.Exists(tempPath))
                {
                    try
                    {
                        File.Delete(tempPath);
                    }
                    catch
                    {
                        // 忽略清理失败
                    }
                }
                throw;
            }
        }
        /// <summary>
        /// 将实体转换为数据模型
        /// </summary>
        private InstanceDataModel ToDataModel(InstanceConfig config)