wanshenmean
2026-03-17 737dec3c384f394fd6f9849b4480b697d1ba35d5
Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Core/Persistence/FilePersistenceService.cs
@@ -1,8 +1,10 @@
using System;
using 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>
    /// 文件持久化服务实现
    /// 将实例配置和内存数据保存到本地JSON文件
    /// 鏂囦欢鎸佷箙鍖栨湇鍔″疄鐜?
    /// 灏嗗疄渚嬮厤缃拰鍐呭瓨鏁版嵁淇濆瓨鍒版湰鍦癑SON鏂囦欢
    /// </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("瀹炰緥ID涓嶈兘涓虹┖", 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("瀹炰緥ID涓嶈兘涓虹┖", 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("瀹炰緥ID涓嶈兘涓虹┖", 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字符串以便JSON序列化
            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("瀹炰緥ID涓嶈兘涓虹┖", 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;
            // 将Base64字符串转换回字节数组
            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)
                {
                    // 跳过无效的Base64数据
                    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>
        /// 将实体转换为数据模型
        /// 楠岃瘉瀹炰緥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 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 @@
        }
    }
}