diff --git a/GFramework.Game/storage/FileStorage.cs b/GFramework.Game/storage/FileStorage.cs
index e5c1d63..cb92eeb 100644
--- a/GFramework.Game/storage/FileStorage.cs
+++ b/GFramework.Game/storage/FileStorage.cs
@@ -43,6 +43,17 @@ public sealed class FileStorage : IStorage
#endregion
+ ///
+ /// 清理文件段字符串,将其中的无效文件名字符替换为下划线
+ ///
+ /// 需要清理的文件段字符串
+ /// 清理后的字符串,其中所有无效文件名字符都被替换为下划线
+ private static string SanitizeSegment(string segment)
+ {
+ return Path.GetInvalidFileNameChars().Aggregate(segment, (current, c) => current.Replace(c, '_'));
+ }
+
+
#region Helpers
///
@@ -52,9 +63,35 @@ public sealed class FileStorage : IStorage
/// 对应的文件路径
private string ToPath(string key)
{
- // 防止非法路径
- key = Path.GetInvalidFileNameChars().Aggregate(key, (current, c) => current.Replace(c, '_'));
- return Path.Combine(_rootPath, $"{key}{_extension}");
+ 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
diff --git a/GFramework.Game/storage/ReadMe.md b/GFramework.Game/storage/ReadMe.md
new file mode 100644
index 0000000..68ffb2f
--- /dev/null
+++ b/GFramework.Game/storage/ReadMe.md
@@ -0,0 +1,130 @@
+# GFramework 存储模块使用指南
+
+本模块提供了基于文件系统的存储功能,包括两个主要类:[FileStorage](./FileStorage.cs)
+和 [ScopedStorage](./ScopedStorage.cs)。
+
+## FileStorage
+
+[FileStorage](./FileStorage.cs) 是一个基于文件系统的存储实现,它将数据以序列化形式保存到磁盘上的指定目录。
+
+### 特性
+
+- 将数据保存为文件(默认扩展名为 `.dat`)
+- 支持同步和异步操作
+- 自动创建存储目录
+- 防止路径遍历攻击(过滤非法文件名字符)
+
+### 构造函数
+
+```csharp
+FileStorage(string rootPath, ISerializer serializer, string extension = ".dat")
+```
+
+### 使用示例
+
+```csharp
+// 创建 JSON 序列化器
+var serializer = new JsonSerializer();
+
+// 创建文件存储实例
+var storage = new FileStorage(@"C:\MyGame\Data", serializer);
+
+// 写入数据
+storage.Write("player_score", 1000);
+storage.Write("player_settings", new { Volume = 0.8f, Difficulty = "Hard" });
+
+// 读取数据
+int score = storage.Read("player_score");
+var settings = storage.Read("player_settings", new { Volume = 0.5f, Difficulty = "Normal" });
+
+// 异步读取数据
+Task scoreTask = storage.ReadAsync("player_score");
+
+// 检查数据是否存在
+if (storage.Exists("player_score"))
+{
+ // 数据存在
+}
+
+// 异步检查数据是否存在
+Task existsTask = storage.ExistsAsync("player_score");
+
+// 删除数据
+storage.Delete("player_score");
+
+// 异步写入数据
+storage.WriteAsync("player_score", 1200);
+```
+
+## ScopedStorage
+
+[ScopedStorage](./ScopedStorage.cs) 是一个装饰器模式的实现,它为所有存储键添加前缀,从而实现逻辑分组和命名空间隔离。
+
+### 特性
+
+- 为所有键添加指定前缀
+- 透明地包装底层存储实现
+- 支持嵌套作用域
+- 与底层存储共享物理存储
+- 支持同步和异步操作
+
+### 构造函数
+
+```csharp
+ScopedStorage(IStorage inner, string prefix)
+```
+
+### 使用示例
+
+```csharp
+// 基于文件存储创建带作用域的存储
+var scopedStorage = new ScopedStorage(fileStorage, "game_settings");
+
+// 所有操作都会添加前缀 "game_settings/"
+scopedStorage.Write("volume", 0.8f); // 实际存储为 "game_settings/volume.dat"
+scopedStorage.Write("theme", "dark"); // 实际存储为 "game_settings/theme.dat"
+
+// 读取操作同样适用前缀
+float volume = scopedStorage.Read("volume"); // 从 "game_settings/volume.dat" 读取
+
+// 创建嵌套作用域
+var nestedStorage = scopedStorage.Scope("graphics"); // 前缀变为 "game_settings/graphics/"
+nestedStorage.Write("resolution", "1920x1080"); // 实际存储为 "game_settings/graphics/resolution.dat"
+
+// 异步操作
+await scopedStorage.WriteAsync("audio", new { MasterVolume = 0.9f });
+var audioSettings = await scopedStorage.ReadAsync