From 737dec3c384f394fd6f9849b4480b697d1ba35d5 Mon Sep 17 00:00:00 2001
From: wanshenmean <cathay_xy@163.com>
Date: 星期二, 17 三月 2026 09:16:44 +0800
Subject: [PATCH] chore: 提交所有当前改动

---
 Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Core/Persistence/FilePersistenceService.cs |  371 ++++++++++++++++++++++++++++++++++++----------------
 1 files changed, 253 insertions(+), 118 deletions(-)

diff --git a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Core/Persistence/FilePersistenceService.cs b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Core/Persistence/FilePersistenceService.cs
index a188c0a..8a6c694 100644
--- a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Core/Persistence/FilePersistenceService.cs
+++ b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Core/Persistence/FilePersistenceService.cs
@@ -1,8 +1,10 @@
-using System;
+锘縰sing 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;
@@ -12,213 +14,307 @@
 namespace WIDESEAWCS_S7Simulator.Core.Persistence
 {
     /// <summary>
-    /// 鏂囦欢鎸佷箙鍖栨湇鍔″疄鐜�
-    /// 灏嗗疄渚嬮厤缃拰鍐呭瓨鏁版嵁淇濆瓨鍒版湰鍦癑SON鏂囦欢
+    /// 閺傚洣娆㈤幐浣风畽閸栨牗婀囬崝鈥崇杽閻�?
+    /// 鐏忓棗鐤勬笟瀣帳缂冾喖鎷伴崘鍛摠閺佺増宓佹穱婵嗙摠閸掔増婀伴崷鐧慡ON閺傚洣娆�
     /// </summary>
     public class FilePersistenceService : IPersistenceService
     {
         /// <summary>
-        /// 鏁版嵁鐩綍璺緞
+        /// 閺佺増宓侀惄顔肩秿鐠侯垰绶�
         /// </summary>
         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>
-        /// <param name="dataPath">鏁版嵁鐩綍璺緞</param>
+        private readonly SemaphoreSlim _fileLock = new SemaphoreSlim(1, 1);
+
+        /// <summary>
+        /// 閺嬪嫰鈧姴鍤遍弫?
+        /// </summary>
+        /// <param name="dataPath">閺佺増宓侀惄顔肩秿鐠侯垰绶�</param>
         public FilePersistenceService(string dataPath = "Data")
         {
-            _dataPath = dataPath;
-            _jsonOptions = new JsonSerializerOptions
-            {
-                WriteIndented = true,
-                PropertyNamingPolicy = JsonNamingPolicy.CamelCase
-            };
+            // 鏉烆剚宕叉稉铏圭卜鐎电鐭惧鍕剁礄閸╄桨绨ぐ鎾冲瀹搞儰缍旈惄顔肩秿閿�?
+            _dataPath = Path.GetFullPath(dataPath);
 
-            // 纭繚鏁版嵁鐩綍瀛樺湪
-            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;
             }
         }
 
         /// <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))
+            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>
-        /// 鍔犺浇瀹炰緥閰嶇疆
+        /// 閸旂姾娴囩�圭偘绶ラ柊宥囩枂
         /// </summary>
         public async Task<InstanceConfig> LoadInstanceConfigAsync(string instanceId)
         {
             if (string.IsNullOrWhiteSpace(instanceId))
-                throw new ArgumentException("瀹炰緥ID涓嶈兘涓虹┖", nameof(instanceId));
+                throw new ArgumentException("鐎圭偘绶D娑撳秷鍏樻稉铏光敄", 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>
-        /// 鍔犺浇鎵�鏈夊疄渚嬮厤缃�
+        /// 閸旂姾娴囬幍鈧張澶婄杽娓氬鍘ょ純?
         /// </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));
+                throw new ArgumentException("鐎圭偘绶D娑撳秷鍏樻稉铏光敄", nameof(instanceId));
 
-            var instanceDir = GetInstanceDirectory(instanceId);
-            if (Directory.Exists(instanceDir))
+            ValidateInstanceId(instanceId);
+
+            await _fileLock.WaitAsync();
+            try
             {
-                Directory.Delete(instanceDir, recursive: true);
+                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>
-        /// 淇濆瓨鍐呭瓨鏁版嵁
+        /// 娣囨繂鐡ㄩ崘鍛摠閺佺増宓�
         /// </summary>
         public async Task SaveMemoryDataAsync(string instanceId, IMemoryStore memoryStore)
         {
             if (string.IsNullOrWhiteSpace(instanceId))
-                throw new ArgumentException("瀹炰緥ID涓嶈兘涓虹┖", nameof(instanceId));
+                throw new ArgumentException("鐎圭偘绶D娑撳秷鍏樻稉铏光敄", 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鐎涙顑佹稉韫簰娓氱竻SON鎼村繐鍨崠?
+                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瀛楃涓蹭互渚縅SON搴忓垪鍖�
-            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>
-        /// 鍔犺浇鍐呭瓨鏁版嵁
+        /// 閸旂姾娴囬崘鍛摠閺佺増宓�
         /// </summary>
         public async Task LoadMemoryDataAsync(string instanceId, IMemoryStore memoryStore)
         {
             if (string.IsNullOrWhiteSpace(instanceId))
-                throw new ArgumentException("瀹炰緥ID涓嶈兘涓虹┖", nameof(instanceId));
+                throw new ArgumentException("鐎圭偘绶D娑撳秷鍏樻稉铏光敄", 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;
-
-            // 灏咮ase64瀛楃涓茶浆鎹㈠洖瀛楄妭鏁扮粍
-            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)
-                {
-                    // 璺宠繃鏃犳晥鐨凚ase64鏁版嵁
-                    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;
+
+                // 鐏忓挳ase64鐎涙顑佹稉鑼舵祮閹广垹娲栫�涙濡弫鎵矋
+                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}");
+                        // 鐠哄疇绻冮弮鐘虫櫏閻ㄥ嚉ase64閺佺増宓�
+                        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>
-        /// 鑾峰彇瀹炰緥鐩綍璺緞
+        /// 閼惧嘲褰囩�圭偘绶ラ惄顔肩秿鐠侯垰绶�
         /// </summary>
         private string GetInstanceDirectory(string instanceId)
         {
@@ -226,7 +322,41 @@
         }
 
         /// <summary>
-        /// 灏嗗疄浣撹浆鎹负鏁版嵁妯″瀷
+        /// 妤犲矁鐦夌�圭偘绶D閿涘牓妲诲銏g熅瀵板嫰浜堕崢鍡樻暰閸戜紮绱�
+        /// </summary>
+        private void ValidateInstanceId(string instanceId)
+        {
+            if (string.IsNullOrWhiteSpace(instanceId))
+                throw new ArgumentException("鐎圭偘绶D娑撳秷鍏樻稉铏光敄", nameof(instanceId));
+
+            // 濡偓閺屻儴鐭惧鍕憾閸樺棗鐡х粭?
+            if (instanceId.Contains("..") || instanceId.Contains("/") || instanceId.Contains("\\"))
+                throw new ArgumentException("鐎圭偘绶D閸栧懎鎯堥棃鐐寸《鐎涙顑�", nameof(instanceId));
+
+            // 濡偓閺屻儲妫ら弫鍫g熅瀵板嫬鐡х粭?
+            var invalidChars = Path.GetInvalidFileNameChars();
+            if (instanceId.IndexOfAny(invalidChars) >= 0)
+                throw new ArgumentException("鐎圭偘绶D閸栧懎鎯堥棃鐐寸《鐎涙顑�", nameof(instanceId));
+        }
+
+        /// <summary>
+        /// 閸愭瑥鍙嗛弬鍥︽閿涘牏娲块幒銉ュ晸閸忋儻绱濈粻鈧崠鏍閺堫剨绱�
+        /// </summary>
+        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);
+        }
+
+        /// <summary>
+        /// 鐏忓棗鐤勬担鎾规祮閹诡澀璐熼弫鐗堝祦濡�崇��
         /// </summary>
         private InstanceDataModel ToDataModel(InstanceConfig config)
         {
@@ -238,10 +368,12 @@
                 Port = config.Port,
                 ActivationKey = config.ActivationKey,
                 AutoStart = config.AutoStart,
+                ProtocolTemplateId = config.ProtocolTemplateId,
                 MemoryConfig = new MemoryRegionConfigModel
                 {
                     MRegionSize = config.MemoryConfig.MRegionSize,
                     DBBlockCount = config.MemoryConfig.DBBlockCount,
+                    DBBlockNumbers = config.MemoryConfig.DBBlockNumbers.ToList(),
                     DBBlockSize = config.MemoryConfig.DBBlockSize,
                     IRegionSize = config.MemoryConfig.IRegionSize,
                     QRegionSize = config.MemoryConfig.QRegionSize,
@@ -252,7 +384,7 @@
         }
 
         /// <summary>
-        /// 灏嗘暟鎹ā鍨嬭浆鎹负瀹炰綋
+        /// 鐏忓棙鏆熼幑顔侥侀崹瀣祮閹诡澀璐熺�圭偘缍�
         /// </summary>
         private InstanceConfig ToEntity(InstanceDataModel model)
         {
@@ -264,10 +396,12 @@
                 Port = model.Port,
                 ActivationKey = model.ActivationKey,
                 AutoStart = model.AutoStart,
+                ProtocolTemplateId = model.ProtocolTemplateId,
                 MemoryConfig = new MemoryRegionConfig
                 {
                     MRegionSize = model.MemoryConfig.MRegionSize,
                     DBBlockCount = model.MemoryConfig.DBBlockCount,
+                    DBBlockNumbers = model.MemoryConfig.DBBlockNumbers?.ToList() ?? new List<int>(),
                     DBBlockSize = model.MemoryConfig.DBBlockSize,
                     IRegionSize = model.MemoryConfig.IRegionSize,
                     QRegionSize = model.MemoryConfig.QRegionSize,
@@ -278,3 +412,4 @@
         }
     }
 }
+

--
Gitblit v1.9.3