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