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