From 5dc4feeff278830e0428e7f7525d570546888c42 Mon Sep 17 00:00:00 2001
From: GwWuYou <95328647+GeWuYou@users.noreply.github.com>
Date: Sun, 11 Jan 2026 20:38:53 +0800
Subject: [PATCH] =?UTF-8?q?docs(storage):=20=E6=9B=B4=E6=96=B0=E5=AD=98?=
=?UTF-8?q?=E5=82=A8=E6=A8=A1=E5=9D=97=E6=96=87=E6=A1=A3=E5=B9=B6=E5=A2=9E?=
=?UTF-8?q?=E5=BC=BA=E6=96=87=E4=BB=B6=E8=B7=AF=E5=BE=84=E5=AE=89=E5=85=A8?=
=?UTF-8?q?=E6=80=A7?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 添加了完整的 GFramework 存储模块使用指南文档
- 实现了 SanitizeSegment 方法用于清理文件段中的无效字符
- 增强了 ToPath 方法的安全性验证,防止路径逃逸攻击
- 添加了对空键值和包含 .. 字符的键的异常处理
- 实现了路径分段处理和目录自动创建功能
- 统一了路径分隔符处理,支持正斜杠和反斜杠
- 添加了详细的使用示例和注意事项说明
---
GFramework.Game/storage/FileStorage.cs | 43 +++++++-
GFramework.Game/storage/ReadMe.md | 130 +++++++++++++++++++++++++
2 files changed, 170 insertions(+), 3 deletions(-)
create mode 100644 GFramework.Game/storage/ReadMe.md
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