wanshenmean
2026-03-17 737dec3c384f394fd6f9849b4480b697d1ba35d5
Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Core/Persistence/FilePersistenceService.cs
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -14,18 +14,18 @@
namespace WIDESEAWCS_S7Simulator.Core.Persistence
{
    /// <summary>
    /// 文件持久化服务实现
    /// 将实例配置和内存数据保存到本地JSON文件
    /// 鏂囦欢鎸佷箙鍖栨湇鍔″疄鐜?
    /// 灏嗗疄渚嬮厤缃拰鍐呭瓨鏁版嵁淇濆瓨鍒版湰鍦癑SON鏂囦欢
    /// </summary>
    public class FilePersistenceService : IPersistenceService
    {
        /// <summary>
        /// 数据目录路径
        /// 鏁版嵁鐩綍璺緞
        /// </summary>
        private readonly string _dataPath;
        /// <summary>
        /// JSON序列化选项(线程安全)
        /// JSON搴忓垪鍖栭€夐」锛堢嚎绋嬪畨鍏級
        /// </summary>
        private static readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions
        {
@@ -34,21 +34,22 @@
        };
        /// <summary>
        /// 文件操作锁(线程安全)
        /// 鏂囦欢鎿嶄綔閿侊紙绾跨▼瀹夊叏锛?
        /// </summary>
        private readonly SemaphoreSlim _fileLock = new SemaphoreSlim(1, 1);
        /// <summary>
        /// 构造函数
        /// 鏋勯€犲嚱鏁?
        /// </summary>
        /// <param name="dataPath">数据目录路径</param>
        /// <param name="dataPath">鏁版嵁鐩綍璺緞</param>
        public FilePersistenceService(string dataPath = "Data")
        {
            _dataPath = dataPath;
            // 杞崲涓虹粷瀵硅矾寰勶紙鍩轰簬褰撳墠宸ヤ綔鐩綍锛?
            _dataPath = Path.GetFullPath(dataPath);
            try
            {
                // 确保数据目录存在
                // 纭繚鏁版嵁鐩綍瀛樺湪
                if (!Directory.Exists(_dataPath))
                {
                    Directory.CreateDirectory(_dataPath);
@@ -62,7 +63,7 @@
        }
        /// <summary>
        /// 保存实例配置
        /// 淇濆瓨瀹炰緥閰嶇疆
        /// </summary>
        public async Task SaveInstanceConfigAsync(InstanceConfig config)
        {
@@ -96,12 +97,12 @@
        }
        /// <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));
            ValidateInstanceId(instanceId);
@@ -110,13 +111,13 @@
            {
                var configPath = Path.Combine(GetInstanceDirectory(instanceId), "config.json");
                if (!File.Exists(configPath))
                    throw new FileNotFoundException($"实例配置文件不存在: {configPath}");
                    throw new FileNotFoundException($"瀹炰緥閰嶇疆鏂囦欢涓嶅瓨鍦? {configPath}");
                var json = await File.ReadAllTextAsync(configPath);
                var model = JsonSerializer.Deserialize<InstanceDataModel>(json, _jsonOptions);
                if (model == null)
                    throw new InvalidOperationException("无法反序列化实例配置");
                    throw new InvalidOperationException("鏃犳硶鍙嶅簭鍒楀寲瀹炰緥閰嶇疆");
                return ToEntity(model);
            }
@@ -132,7 +133,7 @@
        }
        /// <summary>
        /// 加载所有实例配置
        /// 鍔犺浇鎵€鏈夊疄渚嬮厤缃?
        /// </summary>
        public async Task<List<InstanceConfig>> LoadAllInstanceConfigsAsync()
        {
@@ -165,7 +166,7 @@
                        catch (Exception ex)
                        {
                            Console.WriteLine($"Error loading instance config from '{configPath}': {ex.Message}");
                            // 跳过无法加载的配置文件
                            // 璺宠繃鏃犳硶鍔犺浇鐨勯厤缃枃浠?
                            continue;
                        }
                    }
@@ -180,12 +181,12 @@
        }
        /// <summary>
        /// 删除实例配置
        /// 鍒犻櫎瀹炰緥閰嶇疆
        /// </summary>
        public async Task DeleteInstanceConfigAsync(string instanceId)
        {
            if (string.IsNullOrWhiteSpace(instanceId))
                throw new ArgumentException("实例ID不能为空", nameof(instanceId));
                throw new ArgumentException("瀹炰緥ID涓嶈兘涓虹┖", nameof(instanceId));
            ValidateInstanceId(instanceId);
@@ -210,12 +211,12 @@
        }
        /// <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);
@@ -234,7 +235,7 @@
                var memoryPath = Path.Combine(instanceDir, "memory.json");
                var exportedData = memoryStore.Export();
                // 将字节数组转换为Base64字符串以便JSON序列化
                // 灏嗗瓧鑺傛暟缁勮浆鎹负Base64瀛楃涓蹭互渚縅SON搴忓垪鍖?
                var memoryDataModel = new MemoryDataModel
                {
                    MemoryData = exportedData.ToDictionary(
@@ -258,12 +259,12 @@
        }
        /// <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);
@@ -275,7 +276,7 @@
            {
                var memoryPath = Path.Combine(GetInstanceDirectory(instanceId), "memory.json");
                if (!File.Exists(memoryPath))
                    return; // 内存文件不存在,跳过加载
                    return; // 鍐呭瓨鏂囦欢涓嶅瓨鍦紝璺宠繃鍔犺浇
                var json = await File.ReadAllTextAsync(memoryPath);
                var memoryDataModel = JsonSerializer.Deserialize<MemoryDataModel>(json, _jsonOptions);
@@ -283,7 +284,7 @@
                if (memoryDataModel?.MemoryData == null)
                    return;
                // 将Base64字符串转换回字节数组
                // 灏咮ase64瀛楃涓茶浆鎹㈠洖瀛楄妭鏁扮粍
                var importedData = new Dictionary<string, byte[]>();
                foreach (var kvp in memoryDataModel.MemoryData)
                {
@@ -294,7 +295,7 @@
                    catch (FormatException ex)
                    {
                        Console.WriteLine($"Warning: Invalid Base64 data for memory region '{kvp.Key}' in instance '{instanceId}': {ex.Message}");
                        // 跳过无效的Base64数据
                        // 璺宠繃鏃犳晥鐨凚ase64鏁版嵁
                        continue;
                    }
                }
@@ -313,7 +314,7 @@
        }
        /// <summary>
        /// 获取实例目录路径
        /// 鑾峰彇瀹炰緥鐩綍璺緞
        /// </summary>
        private string GetInstanceDirectory(string instanceId)
        {
@@ -321,58 +322,41 @@
        }
        /// <summary>
        /// 验证实例ID(防止路径遍历攻击)
        /// 楠岃瘉瀹炰緥ID锛堥槻姝㈣矾寰勯亶鍘嗘敾鍑伙級
        /// </summary>
        private void ValidateInstanceId(string instanceId)
        {
            if (string.IsNullOrWhiteSpace(instanceId))
                throw new ArgumentException("实例ID不能为空", nameof(instanceId));
                throw new ArgumentException("瀹炰緥ID涓嶈兘涓虹┖", nameof(instanceId));
            // 检查路径遍历字符
            // 妫€鏌ヨ矾寰勯亶鍘嗗瓧绗?
            if (instanceId.Contains("..") || instanceId.Contains("/") || instanceId.Contains("\\"))
                throw new ArgumentException("实例ID包含非法字符", nameof(instanceId));
                throw new ArgumentException("瀹炰緥ID鍖呭惈闈炴硶瀛楃", nameof(instanceId));
            // 检查无效路径字符
            // 妫€鏌ユ棤鏁堣矾寰勫瓧绗?
            var invalidChars = Path.GetInvalidFileNameChars();
            if (instanceId.IndexOfAny(invalidChars) >= 0)
                throw new ArgumentException("实例ID包含非法字符", nameof(instanceId));
                throw new ArgumentException("瀹炰緥ID鍖呭惈闈炴硶瀛楃", nameof(instanceId));
        }
        /// <summary>
        /// 原子性写入文件(使用临时文件+替换模式)
        /// 鍐欏叆鏂囦欢锛堢洿鎺ュ啓鍏ワ紝绠€鍖栫増鏈級
        /// </summary>
        private async Task WriteFileAtomicAsync(string filePath, string content)
        {
            var tempPath = filePath + ".tmp";
            try
            // 纭繚鐩爣鏂囦欢鐨勭埗鐩綍瀛樺湪
            var directory = Path.GetDirectoryName(filePath);
            if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
            {
                // 写入临时文件
                await File.WriteAllTextAsync(tempPath, content, Encoding.UTF8);
                Directory.CreateDirectory(directory);
            }
                // 原子性替换目标文件
                File.Replace(tempPath, filePath, null);
            }
            catch
            {
                // 清理临时文件
                if (File.Exists(tempPath))
                {
                    try
                    {
                        File.Delete(tempPath);
                    }
                    catch
                    {
                        // 忽略清理失败
                    }
                }
                throw;
            }
            // 鐩存帴鍐欏叆鏂囦欢
            await File.WriteAllTextAsync(filePath, content, Encoding.UTF8);
        }
        /// <summary>
        /// 将实体转换为数据模型
        /// 灏嗗疄浣撹浆鎹负鏁版嵁妯″瀷
        /// </summary>
        private InstanceDataModel ToDataModel(InstanceConfig config)
        {
@@ -384,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,
@@ -398,7 +384,7 @@
        }
        /// <summary>
        /// 将数据模型转换为实体
        /// 灏嗘暟鎹ā鍨嬭浆鎹负瀹炰綋
        /// </summary>
        private InstanceConfig ToEntity(InstanceDataModel model)
        {
@@ -410,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,
@@ -424,3 +412,4 @@
        }
    }
}