From 0b3d9467bfd081f607c7b17642e6c017aa32678d Mon Sep 17 00:00:00 2001
From: wanshenmean <cathay_xy@163.com>
Date: 星期五, 13 三月 2026 13:40:02 +0800
Subject: [PATCH] feat: implement file-based persistence service

---
 Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Core/Interfaces/IPersistenceService.cs       |   54 +++++++
 Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Core/Persistence/Models/InstanceDataModel.cs |   68 +++++++++
 Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Core/Persistence/FilePersistenceService.cs   |  280 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 402 insertions(+), 0 deletions(-)

diff --git a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Core/Interfaces/IPersistenceService.cs b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Core/Interfaces/IPersistenceService.cs
new file mode 100644
index 0000000..17a1519
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Core/Interfaces/IPersistenceService.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using WIDESEAWCS_S7Simulator.Core.Entities;
+using WIDESEAWCS_S7Simulator.Core.Interfaces;
+
+namespace WIDESEAWCS_S7Simulator.Core.Persistence
+{
+    /// <summary>
+    /// 鎸佷箙鍖栨湇鍔℃帴鍙�
+    /// 鎻愪緵瀹炰緥閰嶇疆鍜屽唴瀛樻暟鎹殑鎸佷箙鍖栧姛鑳�
+    /// </summary>
+    public interface IPersistenceService
+    {
+        /// <summary>
+        /// 淇濆瓨瀹炰緥閰嶇疆
+        /// </summary>
+        /// <param name="config">瀹炰緥閰嶇疆</param>
+        Task SaveInstanceConfigAsync(InstanceConfig config);
+
+        /// <summary>
+        /// 鍔犺浇瀹炰緥閰嶇疆
+        /// </summary>
+        /// <param name="instanceId">瀹炰緥ID</param>
+        /// <returns>瀹炰緥閰嶇疆</returns>
+        Task<InstanceConfig> LoadInstanceConfigAsync(string instanceId);
+
+        /// <summary>
+        /// 鍔犺浇鎵�鏈夊疄渚嬮厤缃�
+        /// </summary>
+        /// <returns>瀹炰緥閰嶇疆鍒楄〃</returns>
+        Task<List<InstanceConfig>> LoadAllInstanceConfigsAsync();
+
+        /// <summary>
+        /// 鍒犻櫎瀹炰緥閰嶇疆
+        /// </summary>
+        /// <param name="instanceId">瀹炰緥ID</param>
+        Task DeleteInstanceConfigAsync(string instanceId);
+
+        /// <summary>
+        /// 淇濆瓨鍐呭瓨鏁版嵁
+        /// </summary>
+        /// <param name="instanceId">瀹炰緥ID</param>
+        /// <param name="memoryStore">鍐呭瓨瀛樺偍</param>
+        Task SaveMemoryDataAsync(string instanceId, IMemoryStore memoryStore);
+
+        /// <summary>
+        /// 鍔犺浇鍐呭瓨鏁版嵁
+        /// </summary>
+        /// <param name="instanceId">瀹炰緥ID</param>
+        /// <param name="memoryStore">鍐呭瓨瀛樺偍</param>
+        Task LoadMemoryDataAsync(string instanceId, IMemoryStore memoryStore);
+    }
+}
diff --git a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Core/Persistence/FilePersistenceService.cs b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Core/Persistence/FilePersistenceService.cs
new file mode 100644
index 0000000..a188c0a
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Core/Persistence/FilePersistenceService.cs
@@ -0,0 +1,280 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.Json;
+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
+{
+    /// <summary>
+    /// 鏂囦欢鎸佷箙鍖栨湇鍔″疄鐜�
+    /// 灏嗗疄渚嬮厤缃拰鍐呭瓨鏁版嵁淇濆瓨鍒版湰鍦癑SON鏂囦欢
+    /// </summary>
+    public class FilePersistenceService : IPersistenceService
+    {
+        /// <summary>
+        /// 鏁版嵁鐩綍璺緞
+        /// </summary>
+        private readonly string _dataPath;
+
+        /// <summary>
+        /// JSON搴忓垪鍖栭�夐」
+        /// </summary>
+        private readonly JsonSerializerOptions _jsonOptions;
+
+        /// <summary>
+        /// 鏋勯�犲嚱鏁�
+        /// </summary>
+        /// <param name="dataPath">鏁版嵁鐩綍璺緞</param>
+        public FilePersistenceService(string dataPath = "Data")
+        {
+            _dataPath = dataPath;
+            _jsonOptions = new JsonSerializerOptions
+            {
+                WriteIndented = true,
+                PropertyNamingPolicy = JsonNamingPolicy.CamelCase
+            };
+
+            // 纭繚鏁版嵁鐩綍瀛樺湪
+            if (!Directory.Exists(_dataPath))
+            {
+                Directory.CreateDirectory(_dataPath);
+            }
+        }
+
+        /// <summary>
+        /// 淇濆瓨瀹炰緥閰嶇疆
+        /// </summary>
+        public async Task SaveInstanceConfigAsync(InstanceConfig config)
+        {
+            if (config == null)
+                throw new ArgumentNullException(nameof(config));
+
+            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 File.WriteAllTextAsync(configPath, json);
+        }
+
+        /// <summary>
+        /// 鍔犺浇瀹炰緥閰嶇疆
+        /// </summary>
+        public async Task<InstanceConfig> LoadInstanceConfigAsync(string instanceId)
+        {
+            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}");
+
+            var json = await File.ReadAllTextAsync(configPath);
+            var model = JsonSerializer.Deserialize<InstanceDataModel>(json, _jsonOptions);
+
+            if (model == null)
+                throw new InvalidOperationException("鏃犳硶鍙嶅簭鍒楀寲瀹炰緥閰嶇疆");
+
+            return ToEntity(model);
+        }
+
+        /// <summary>
+        /// 鍔犺浇鎵�鏈夊疄渚嬮厤缃�
+        /// </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)
+            {
+                var configPath = Path.Combine(dir, "config.json");
+                if (File.Exists(configPath))
+                {
+                    try
+                    {
+                        var json = await File.ReadAllTextAsync(configPath);
+                        var model = JsonSerializer.Deserialize<InstanceDataModel>(json, _jsonOptions);
+                        if (model != null)
+                        {
+                            configs.Add(ToEntity(model));
+                        }
+                    }
+                    catch (Exception)
+                    {
+                        // 璺宠繃鏃犳硶鍔犺浇鐨勯厤缃枃浠�
+                        continue;
+                    }
+                }
+            }
+
+            return configs;
+        }
+
+        /// <summary>
+        /// 鍒犻櫎瀹炰緥閰嶇疆
+        /// </summary>
+        public 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);
+            }
+
+            return Task.CompletedTask;
+        }
+
+        /// <summary>
+        /// 淇濆瓨鍐呭瓨鏁版嵁
+        /// </summary>
+        public async Task SaveMemoryDataAsync(string instanceId, IMemoryStore memoryStore)
+        {
+            if (string.IsNullOrWhiteSpace(instanceId))
+                throw new ArgumentException("瀹炰緥ID涓嶈兘涓虹┖", nameof(instanceId));
+
+            if (memoryStore == null)
+                throw new ArgumentNullException(nameof(memoryStore));
+
+            var instanceDir = GetInstanceDirectory(instanceId);
+            if (!Directory.Exists(instanceDir))
+            {
+                Directory.CreateDirectory(instanceDir);
+            }
+
+            var memoryPath = Path.Combine(instanceDir, "memory.json");
+            var exportedData = memoryStore.Export();
+
+            // 灏嗗瓧鑺傛暟缁勮浆鎹负Base64瀛楃涓蹭互渚縅SON搴忓垪鍖�
+            var memoryDataModel = new MemoryDataModel
+            {
+                MemoryData = exportedData.ToDictionary(
+                    kvp => kvp.Key,
+                    kvp => Convert.ToBase64String(kvp.Value)
+                )
+            };
+
+            var json = JsonSerializer.Serialize(memoryDataModel, _jsonOptions);
+            await File.WriteAllTextAsync(memoryPath, json);
+        }
+
+        /// <summary>
+        /// 鍔犺浇鍐呭瓨鏁版嵁
+        /// </summary>
+        public async Task LoadMemoryDataAsync(string instanceId, IMemoryStore memoryStore)
+        {
+            if (string.IsNullOrWhiteSpace(instanceId))
+                throw new ArgumentException("瀹炰緥ID涓嶈兘涓虹┖", nameof(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;
+
+            // 灏咮ase64瀛楃涓茶浆鎹㈠洖瀛楄妭鏁扮粍
+            var importedData = new Dictionary<string, byte[]>();
+            foreach (var kvp in memoryDataModel.MemoryData)
+            {
+                try
+                {
+                    importedData[kvp.Key] = Convert.FromBase64String(kvp.Value);
+                }
+                catch (FormatException)
+                {
+                    // 璺宠繃鏃犳晥鐨凚ase64鏁版嵁
+                    continue;
+                }
+            }
+
+            memoryStore.Import(importedData);
+        }
+
+        /// <summary>
+        /// 鑾峰彇瀹炰緥鐩綍璺緞
+        /// </summary>
+        private string GetInstanceDirectory(string instanceId)
+        {
+            return Path.Combine(_dataPath, $"instance-{instanceId}");
+        }
+
+        /// <summary>
+        /// 灏嗗疄浣撹浆鎹负鏁版嵁妯″瀷
+        /// </summary>
+        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
+                }
+            };
+        }
+
+        /// <summary>
+        /// 灏嗘暟鎹ā鍨嬭浆鎹负瀹炰綋
+        /// </summary>
+        private InstanceConfig ToEntity(InstanceDataModel model)
+        {
+            return new InstanceConfig
+            {
+                Id = model.Id,
+                Name = model.Name,
+                PLCType = Enum.Parse<SiemensPLCType>(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
+                }
+            };
+        }
+    }
+}
diff --git a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Core/Persistence/Models/InstanceDataModel.cs b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Core/Persistence/Models/InstanceDataModel.cs
new file mode 100644
index 0000000..2963c81
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Core/Persistence/Models/InstanceDataModel.cs
@@ -0,0 +1,68 @@
+namespace WIDESEAWCS_S7Simulator.Core.Persistence.Models
+{
+    /// <summary>
+    /// 瀹炰緥鏁版嵁妯″瀷锛堢敤浜嶫SON搴忓垪鍖栵級
+    /// </summary>
+    public class InstanceDataModel
+    {
+        /// <summary>
+        /// 瀹炰緥ID
+        /// </summary>
+        public string Id { get; set; } = string.Empty;
+
+        /// <summary>
+        /// 瀹炰緥鍚嶇О
+        /// </summary>
+        public string Name { get; set; } = string.Empty;
+
+        /// <summary>
+        /// PLC绫诲瀷
+        /// </summary>
+        public string PlcType { get; set; } = string.Empty;
+
+        /// <summary>
+        /// 鐩戝惉绔彛
+        /// </summary>
+        public int Port { get; set; }
+
+        /// <summary>
+        /// 婵�娲诲瘑閽�
+        /// </summary>
+        public string ActivationKey { get; set; } = string.Empty;
+
+        /// <summary>
+        /// 鑷姩鍚姩
+        /// </summary>
+        public bool AutoStart { get; set; }
+
+        /// <summary>
+        /// 鍐呭瓨鍖哄煙閰嶇疆
+        /// </summary>
+        public MemoryRegionConfigModel MemoryConfig { get; set; } = new();
+    }
+
+    /// <summary>
+    /// 鍐呭瓨鍖哄煙閰嶇疆妯″瀷
+    /// </summary>
+    public class MemoryRegionConfigModel
+    {
+        public int MRegionSize { get; set; } = 1024;
+        public int DBBlockCount { get; set; } = 100;
+        public int DBBlockSize { get; set; } = 1024;
+        public int IRegionSize { get; set; } = 256;
+        public int QRegionSize { get; set; } = 256;
+        public int TRegionCount { get; set; } = 64;
+        public int CRegionCount { get; set; } = 64;
+    }
+
+    /// <summary>
+    /// 鍐呭瓨鏁版嵁妯″瀷
+    /// </summary>
+    public class MemoryDataModel
+    {
+        /// <summary>
+        /// 鍐呭瓨鍖哄煙鏁版嵁瀛楀吀锛堝尯鍩熺被鍨� -> Base64缂栫爜鐨勫瓧鑺傛暟缁勶級
+        /// </summary>
+        public Dictionary<string, string> MemoryData { get; set; } = new();
+    }
+}

--
Gitblit v1.9.3