mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-25 13:33:28 +08:00
docs(storage): 更新存储模块文档并增强文件路径安全性
- 添加了完整的 GFramework 存储模块使用指南文档 - 实现了 SanitizeSegment 方法用于清理文件段中的无效字符 - 增强了 ToPath 方法的安全性验证,防止路径逃逸攻击 - 添加了对空键值和包含 .. 字符的键的异常处理 - 实现了路径分段处理和目录自动创建功能 - 统一了路径分隔符处理,支持正斜杠和反斜杠 - 添加了详细的使用示例和注意事项说明
This commit is contained in:
parent
c3376bf4d5
commit
5dc4feeff2
@ -43,6 +43,17 @@ public sealed class FileStorage : IStorage
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 清理文件段字符串,将其中的无效文件名字符替换为下划线
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="segment">需要清理的文件段字符串</param>
|
||||||
|
/// <returns>清理后的字符串,其中所有无效文件名字符都被替换为下划线</returns>
|
||||||
|
private static string SanitizeSegment(string segment)
|
||||||
|
{
|
||||||
|
return Path.GetInvalidFileNameChars().Aggregate(segment, (current, c) => current.Replace(c, '_'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#region Helpers
|
#region Helpers
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -52,9 +63,35 @@ public sealed class FileStorage : IStorage
|
|||||||
/// <returns>对应的文件路径</returns>
|
/// <returns>对应的文件路径</returns>
|
||||||
private string ToPath(string key)
|
private string ToPath(string key)
|
||||||
{
|
{
|
||||||
// 防止非法路径
|
if (string.IsNullOrWhiteSpace(key))
|
||||||
key = Path.GetInvalidFileNameChars().Aggregate(key, (current, c) => current.Replace(c, '_'));
|
throw new ArgumentException("Storage key cannot be empty", nameof(key));
|
||||||
return Path.Combine(_rootPath, $"{key}{_extension}");
|
|
||||||
|
// 统一分隔符
|
||||||
|
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
|
#endregion
|
||||||
|
|||||||
130
GFramework.Game/storage/ReadMe.md
Normal file
130
GFramework.Game/storage/ReadMe.md
Normal file
@ -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<int>("player_score");
|
||||||
|
var settings = storage.Read("player_settings", new { Volume = 0.5f, Difficulty = "Normal" });
|
||||||
|
|
||||||
|
// 异步读取数据
|
||||||
|
Task<int> scoreTask = storage.ReadAsync<int>("player_score");
|
||||||
|
|
||||||
|
// 检查数据是否存在
|
||||||
|
if (storage.Exists("player_score"))
|
||||||
|
{
|
||||||
|
// 数据存在
|
||||||
|
}
|
||||||
|
|
||||||
|
// 异步检查数据是否存在
|
||||||
|
Task<bool> 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<float>("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<object>("audio");
|
||||||
|
```
|
||||||
|
|
||||||
|
## 组合使用示例
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 创建基础序列化器和文件存储
|
||||||
|
var serializer = new JsonSerializer();
|
||||||
|
var baseStorage = new FileStorage(@"C:\MyGame\Data", serializer);
|
||||||
|
|
||||||
|
// 创建不同作用域的存储
|
||||||
|
var playerStorage = new ScopedStorage(baseStorage, "player");
|
||||||
|
var gameStorage = new ScopedStorage(baseStorage, "game");
|
||||||
|
var settingsStorage = new ScopedStorage(baseStorage, "settings");
|
||||||
|
|
||||||
|
// 在不同的作用域中使用相同键而不会冲突
|
||||||
|
playerStorage.Write("level", 5);
|
||||||
|
gameStorage.Write("level", "forest_area_1");
|
||||||
|
settingsStorage.Write("level", "high");
|
||||||
|
|
||||||
|
// 结果是三个不同的文件:
|
||||||
|
// - player/level.dat
|
||||||
|
// - game/level.dat
|
||||||
|
// - settings/level.dat
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **序列化器选择**:确保使用的 [ISerializer](../GFramework.Game.Abstractions/serializer/ISerializer.cs)
|
||||||
|
实现能够正确处理你要存储的数据类型。
|
||||||
|
2. **错误处理**:[FileStorage](./FileStorage.cs) 的 `Read<T>(string key)` 方法会在键不存在时抛出异常,可以使用
|
||||||
|
`Read<T>(string key, T defaultValue)` 来避免异常。
|
||||||
|
3. **线程安全**:当前实现不是线程安全的,如需在多线程环境中使用,请添加适当的同步机制。
|
||||||
|
4. **文件权限**:确保应用程序对指定的存储目录有读写权限。
|
||||||
|
5. **路径安全**:[FileStorage](./FileStorage.cs) 会自动防止路径遍历攻击,因此键不能包含 `..`,并且特殊字符会被替换为下划线。
|
||||||
Loading…
x
Reference in New Issue
Block a user