using System.Collections.Concurrent; using System.Text; using GFramework.Game.Abstractions.serializer; using GFramework.Game.Abstractions.storage; namespace GFramework.Game.storage; /// /// 基于文件系统的存储实现,实现了IFileStorage接口,支持按key细粒度锁保证线程安全 /// public sealed class FileStorage : IFileStorage { private readonly string _extension; // 每个key对应的锁对象 private readonly ConcurrentDictionary _keyLocks = new(); private readonly string _rootPath; private readonly ISerializer _serializer; /// /// 初始化FileStorage实例 /// /// 存储根目录路径 /// 序列化器实例 /// 存储文件的扩展名 public FileStorage(string rootPath, ISerializer serializer, string extension = ".dat") { _rootPath = rootPath; _serializer = serializer; _extension = extension; Directory.CreateDirectory(_rootPath); } #region Delete /// /// 删除指定键的存储项 /// /// 存储键 public void Delete(string key) { var path = ToPath(key); var keyLock = _keyLocks.GetOrAdd(path, _ => new object()); lock (keyLock) { if (File.Exists(path)) File.Delete(path); } } #endregion /// /// 清理文件段字符串,将其中的无效文件名字符替换为下划线 /// /// 需要清理的文件段字符串 /// 清理后的字符串,其中所有无效文件名字符都被替换为下划线 private static string SanitizeSegment(string segment) { return Path.GetInvalidFileNameChars().Aggregate(segment, (current, c) => current.Replace(c, '_')); } #region Helpers /// /// 将存储键转换为文件路径 /// /// 存储键 /// 对应的文件路径 private string ToPath(string key) { if (string.IsNullOrWhiteSpace(key)) throw new ArgumentException("Storage key cannot be empty", nameof(key)); // 统一分隔符 key = key.Replace('\\', '/'); // 防止路径逃逸 if (key.Contains("..")) throw new ArgumentException("Storage key cannot contain '..'", nameof(key)); var segments = key .Split('/', StringSplitOptions.RemoveEmptyEntries) .Select(SanitizeSegment) .ToArray(); if (segments.Length == 0) throw new ArgumentException("Invalid storage key", nameof(key)); // 目录部分 var dirSegments = segments[..^1]; var fileName = segments[^1] + _extension; var dirPath = dirSegments.Length == 0 ? _rootPath : Path.Combine(_rootPath, Path.Combine(dirSegments)); Directory.CreateDirectory(dirPath); return Path.Combine(dirPath, fileName); } #endregion #region Exists /// /// 检查指定键的存储项是否存在 /// /// 存储键 /// 如果存储项存在则返回true,否则返回false public bool Exists(string key) { var path = ToPath(key); var keyLock = _keyLocks.GetOrAdd(path, _ => new object()); lock (keyLock) { return File.Exists(path); } } /// /// 异步检查指定键的存储项是否存在 /// /// 存储键 /// 如果存储项存在则返回true,否则返回false public Task ExistsAsync(string key) => Task.FromResult(Exists(key)); #endregion #region Read /// /// 读取指定键的存储项 /// /// 要反序列化的类型 /// 存储键 /// 反序列化后的对象 /// 当存储键不存在时抛出 public T Read(string key) { var path = ToPath(key); var keyLock = _keyLocks.GetOrAdd(path, _ => new object()); lock (keyLock) { if (!File.Exists(path)) throw new FileNotFoundException($"Storage key not found: {key}", path); var content = File.ReadAllText(path, Encoding.UTF8); return _serializer.Deserialize(content); } } /// /// 读取指定键的存储项,如果不存在则返回默认值 /// /// 要反序列化的类型 /// 存储键 /// 当存储键不存在时返回的默认值 /// 反序列化后的对象或默认值 public T Read(string key, T defaultValue) { var path = ToPath(key); var keyLock = _keyLocks.GetOrAdd(path, _ => new object()); lock (keyLock) { if (!File.Exists(path)) return defaultValue; var content = File.ReadAllText(path, Encoding.UTF8); return _serializer.Deserialize(content); } } /// /// 异步读取指定键的存储项 /// /// 要反序列化的类型 /// 存储键 /// 反序列化后的对象 /// 当存储键不存在时抛出 public async Task ReadAsync(string key) { var path = ToPath(key); var keyLock = _keyLocks.GetOrAdd(path, _ => new object()); // 异步操作依然使用lock保护文件读写 lock (keyLock) { if (!File.Exists(path)) throw new FileNotFoundException($"Storage key not found: {key}", path); } // 读取文件内容可以使用异步IO,但要注意锁范围 string content; using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) using (var sr = new StreamReader(fs, Encoding.UTF8)) { content = await sr.ReadToEndAsync(); } return _serializer.Deserialize(content); } #endregion #region Write /// /// 写入指定键的存储项 /// /// 要序列化的对象类型 /// 存储键 /// 要存储的对象 public void Write(string key, T value) { var path = ToPath(key); var keyLock = _keyLocks.GetOrAdd(path, _ => new object()); var content = _serializer.Serialize(value); lock (keyLock) { File.WriteAllText(path, content, Encoding.UTF8); } } /// /// 异步写入指定键的存储项 /// /// 要序列化的对象类型 /// 存储键 /// 要存储的对象 /// 表示异步操作的任务 public async Task WriteAsync(string key, T value) { var path = ToPath(key); var keyLock = _keyLocks.GetOrAdd(path, _ => new object()); var content = _serializer.Serialize(value); // 异步写也需要锁 lock (keyLock) { using var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None); using var sw = new StreamWriter(fs, Encoding.UTF8); sw.WriteAsync(content); } await Task.CompletedTask; } #endregion }