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
            };
            try
            {
            // 确保数据目录存在
            if (!Directory.Exists(_dataPath))
            {
                Directory.CreateDirectory(_dataPath);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error creating data directory '{_dataPath}': {ex.Message}");
                throw;
            }
        }
@@ -55,6 +69,9 @@
            if (config == null)
                throw new ArgumentNullException(nameof(config));
            await _fileLock.WaitAsync();
            try
            {
            var instanceDir = GetInstanceDirectory(config.Id);
            if (!Directory.Exists(instanceDir))
            {
@@ -65,7 +82,17 @@
            var model = ToDataModel(config);
            var json = JsonSerializer.Serialize(model, _jsonOptions);
            await File.WriteAllTextAsync(configPath, json);
                await WriteFileAtomicAsync(configPath, json);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error saving instance config for '{config.Id}': {ex.Message}");
                throw;
            }
            finally
            {
                _fileLock.Release();
            }
        }
        /// <summary>
@@ -76,6 +103,11 @@
            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}");
@@ -88,11 +120,24 @@
            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>
        /// 加载所有实例配置
        /// </summary>
        public async Task<List<InstanceConfig>> LoadAllInstanceConfigsAsync()
        {
            await _fileLock.WaitAsync();
            try
        {
            var configs = new List<InstanceConfig>();
@@ -117,8 +162,9 @@
                            configs.Add(ToEntity(model));
                        }
                    }
                    catch (Exception)
                        catch (Exception ex)
                    {
                            Console.WriteLine($"Error loading instance config from '{configPath}': {ex.Message}");
                        // 跳过无法加载的配置文件
                        continue;
                    }
@@ -127,22 +173,40 @@
            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));
            ValidateInstanceId(instanceId);
            await _fileLock.WaitAsync();
            try
            {
            var instanceDir = GetInstanceDirectory(instanceId);
            if (Directory.Exists(instanceDir))
            {
                Directory.Delete(instanceDir, recursive: true);
            }
            return Task.CompletedTask;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error deleting instance '{instanceId}': {ex.Message}");
                throw;
            }
            finally
            {
                _fileLock.Release();
            }
        }
        /// <summary>
@@ -153,9 +217,14 @@
            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))
            {
@@ -175,7 +244,17 @@
            };
            var json = JsonSerializer.Serialize(memoryDataModel, _jsonOptions);
            await File.WriteAllTextAsync(memoryPath, json);
                await WriteFileAtomicAsync(memoryPath, json);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error saving memory data for instance '{instanceId}': {ex.Message}");
                throw;
            }
            finally
            {
                _fileLock.Release();
            }
        }
        /// <summary>
@@ -186,9 +265,14 @@
            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; // 内存文件不存在,跳过加载
@@ -207,14 +291,25 @@
                {
                    importedData[kvp.Key] = Convert.FromBase64String(kvp.Value);
                }
                catch (FormatException)
                    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)