feat(storage): 添加存储系统接口和文件存储实现

- 定义了IStorage接口提供同步和异步的数据存储操作功能
- 实现了基于文件系统的FileStorage类支持读写删除操作
- 添加了ScopedStorage包装器为存储键提供作用域前缀功能
- 创建了ISerializer接口并实现JsonSerializer使用Newtonsoft.Json
- 在项目中引入Newtonsoft.Json包依赖
This commit is contained in:
GwWuYou 2026-01-11 19:56:31 +08:00
parent 7843e2a14f
commit c3376bf4d5
6 changed files with 395 additions and 0 deletions

View File

@ -0,0 +1,72 @@
using System.Threading.Tasks;
using GFramework.Core.Abstractions.utility;
namespace GFramework.Core.Abstractions.storage;
/// <summary>
/// 存储接口,提供同步和异步的数据存储操作功能
/// </summary>
public interface IStorage : IUtility
{
/// <summary>
/// 检查指定键的存储项是否存在
/// </summary>
/// <param name="key">要检查的键</param>
/// <returns>如果键存在则返回true否则返回false</returns>
bool Exists(string key);
/// <summary>
/// 异步检查指定键的存储项是否存在
/// </summary>
/// <param name="key">要检查的键</param>
/// <returns>如果键存在则返回true否则返回false</returns>
Task<bool> ExistsAsync(string key);
/// <summary>
/// 读取指定键的值
/// </summary>
/// <typeparam name="T">要读取的值的类型</typeparam>
/// <param name="key">要读取的键</param>
/// <returns>指定键对应的值</returns>
T Read<T>(string key);
/// <summary>
/// 读取指定键的值,如果键不存在则返回默认值
/// </summary>
/// <typeparam name="T">要读取的值的类型</typeparam>
/// <param name="key">要读取的键</param>
/// <param name="defaultValue">当键不存在时返回的默认值</param>
/// <returns>指定键对应的值,如果键不存在则返回默认值</returns>
T Read<T>(string key, T defaultValue);
/// <summary>
/// 异步读取指定键的值
/// </summary>
/// <typeparam name="T">要读取的值的类型</typeparam>
/// <param name="key">要读取的键</param>
/// <returns>指定键对应的值</returns>
Task<T> ReadAsync<T>(string key);
/// <summary>
/// 将值写入指定键
/// </summary>
/// <typeparam name="T">要写入的值的类型</typeparam>
/// <param name="key">要写入的键</param>
/// <param name="value">要写入的值</param>
void Write<T>(string key, T value);
/// <summary>
/// 异步将值写入指定键
/// </summary>
/// <typeparam name="T">要写入的值的类型</typeparam>
/// <param name="key">要写入的键</param>
/// <param name="value">要写入的值</param>
/// <returns>异步操作任务</returns>
Task WriteAsync<T>(string key, T value);
/// <summary>
/// 删除指定键的存储项
/// </summary>
/// <param name="key">要删除的键</param>
void Delete(string key);
}

View File

@ -0,0 +1,23 @@
namespace GFramework.Game.Abstractions.serializer;
/// <summary>
/// 定义序列化器接口,提供对象序列化和反序列化的通用方法
/// </summary>
public interface ISerializer
{
/// <summary>
/// 将指定的对象序列化为字符串
/// </summary>
/// <typeparam name="T">要序列化的对象类型</typeparam>
/// <param name="value">要序列化的对象实例</param>
/// <returns>序列化后的字符串表示</returns>
string Serialize<T>(T value);
/// <summary>
/// 将字符串数据反序列化为指定类型的对象
/// </summary>
/// <typeparam name="T">要反序列化的目标对象类型</typeparam>
/// <param name="data">包含序列化数据的字符串</param>
/// <returns>反序列化后的对象实例</returns>
T Deserialize<T>(string data);
}

View File

@ -10,4 +10,7 @@
<ProjectReference Include="..\GFramework.Core\GFramework.Core.csproj"/>
<ProjectReference Include="..\$(AssemblyName).Abstractions\$(AssemblyName).Abstractions.csproj"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
</ItemGroup>
</Project>

View File

@ -0,0 +1,29 @@
using GFramework.Game.Abstractions.serializer;
using Newtonsoft.Json;
namespace GFramework.Game.serializer;
/// <summary>
/// JSON序列化器实现类用于将对象序列化为JSON字符串或将JSON字符串反序列化为对象
/// </summary>
public sealed class JsonSerializer : ISerializer
{
/// <summary>
/// 将指定的对象序列化为JSON字符串
/// </summary>
/// <typeparam name="T">要序列化的对象类型</typeparam>
/// <param name="value">要序列化的对象实例</param>
/// <returns>序列化后的JSON字符串</returns>
public string Serialize<T>(T value)
=> JsonConvert.SerializeObject(value);
/// <summary>
/// 将JSON字符串反序列化为指定类型的对象
/// </summary>
/// <typeparam name="T">要反序列化的对象类型</typeparam>
/// <param name="data">包含JSON数据的字符串</param>
/// <returns>反序列化后的对象实例</returns>
/// <exception cref="ArgumentException">当无法反序列化数据时抛出</exception>
public T Deserialize<T>(string data)
=> JsonConvert.DeserializeObject<T>(data) ?? throw new ArgumentException("Cannot deserialize data");
}

View File

@ -0,0 +1,170 @@
using System.Text;
using GFramework.Core.Abstractions.storage;
using GFramework.Game.Abstractions.serializer;
namespace GFramework.Game.storage;
/// <summary>
/// 基于文件系统的存储实现实现了IStorage接口
/// </summary>
public sealed class FileStorage : IStorage
{
private readonly string _extension;
private readonly string _rootPath;
private readonly ISerializer _serializer;
/// <summary>
/// 初始化FileStorage实例
/// </summary>
/// <param name="rootPath">存储根目录路径</param>
/// <param name="serializer">序列化器实例</param>
/// <param name="extension">存储文件的扩展名</param>
public FileStorage(string rootPath, ISerializer serializer, string extension = ".dat")
{
_rootPath = rootPath;
_serializer = serializer;
_extension = extension;
Directory.CreateDirectory(_rootPath);
}
#region Delete
/// <summary>
/// 删除指定键的存储项
/// </summary>
/// <param name="key">存储键</param>
public void Delete(string key)
{
var path = ToPath(key);
if (File.Exists(path))
File.Delete(path);
}
#endregion
#region Helpers
/// <summary>
/// 将存储键转换为文件路径
/// </summary>
/// <param name="key">存储键</param>
/// <returns>对应的文件路径</returns>
private string ToPath(string key)
{
// 防止非法路径
key = Path.GetInvalidFileNameChars().Aggregate(key, (current, c) => current.Replace(c, '_'));
return Path.Combine(_rootPath, $"{key}{_extension}");
}
#endregion
#region Exists
/// <summary>
/// 检查指定键的存储项是否存在
/// </summary>
/// <param name="key">存储键</param>
/// <returns>存在返回true否则返回false</returns>
public bool Exists(string key)
=> File.Exists(ToPath(key));
/// <summary>
/// 异步检查指定键的存储项是否存在
/// </summary>
/// <param name="key">存储键</param>
/// <returns>存在返回true否则返回false</returns>
public Task<bool> ExistsAsync(string key)
=> Task.FromResult(Exists(key));
#endregion
#region Read
/// <summary>
/// 读取指定键的存储项
/// </summary>
/// <typeparam name="T">要反序列化的类型</typeparam>
/// <param name="key">存储键</param>
/// <returns>反序列化后的对象</returns>
/// <exception cref="FileNotFoundException">当指定键不存在时抛出</exception>
public T Read<T>(string key)
{
var path = ToPath(key);
if (!File.Exists(path))
throw new FileNotFoundException($"Storage key not found: {key}", path);
var content = File.ReadAllText(path, Encoding.UTF8);
return _serializer.Deserialize<T>(content);
}
/// <summary>
/// 读取指定键的存储项,如果不存在则返回默认值
/// </summary>
/// <typeparam name="T">要反序列化的类型</typeparam>
/// <param name="key">存储键</param>
/// <param name="defaultValue">默认值</param>
/// <returns>存在则返回反序列化后的对象,否则返回默认值</returns>
public T Read<T>(string key, T defaultValue)
{
var path = ToPath(key);
if (!File.Exists(path))
return defaultValue;
var content = File.ReadAllText(path, Encoding.UTF8);
return _serializer.Deserialize<T>(content);
}
/// <summary>
/// 异步读取指定键的存储项
/// </summary>
/// <typeparam name="T">要反序列化的类型</typeparam>
/// <param name="key">存储键</param>
/// <returns>反序列化后的对象</returns>
/// <exception cref="FileNotFoundException">当指定键不存在时抛出</exception>
public async Task<T> ReadAsync<T>(string key)
{
var path = ToPath(key);
if (!File.Exists(path))
throw new FileNotFoundException($"Storage key not found: {key}", path);
var content = await File.ReadAllTextAsync(path, Encoding.UTF8);
return _serializer.Deserialize<T>(content);
}
#endregion
#region Write
/// <summary>
/// 写入指定键的存储项
/// </summary>
/// <typeparam name="T">要序列化的类型</typeparam>
/// <param name="key">存储键</param>
/// <param name="value">要存储的值</param>
public void Write<T>(string key, T value)
{
var path = ToPath(key);
var content = _serializer.Serialize(value);
File.WriteAllText(path, content, Encoding.UTF8);
}
/// <summary>
/// 异步写入指定键的存储项
/// </summary>
/// <typeparam name="T">要序列化的类型</typeparam>
/// <param name="key">存储键</param>
/// <param name="value">要存储的值</param>
public async Task WriteAsync<T>(string key, T value)
{
var path = ToPath(key);
var content = _serializer.Serialize(value);
await File.WriteAllTextAsync(path, content, Encoding.UTF8);
}
#endregion
}

View File

@ -0,0 +1,98 @@
using GFramework.Core.Abstractions.storage;
namespace GFramework.Game.storage;
/// <summary>
/// 提供带有作用域前缀的存储包装器,将所有键都加上指定的前缀
/// </summary>
/// <param name="inner">内部的实际存储实现</param>
/// <param name="prefix">用于所有键的前缀字符串</param>
public sealed class ScopedStorage(IStorage inner, string prefix) : IStorage
{
/// <summary>
/// 检查指定键是否存在
/// </summary>
/// <param name="key">要检查的键</param>
/// <returns>如果键存在则返回true否则返回false</returns>
public bool Exists(string key)
=> inner.Exists(Key(key));
/// <summary>
/// 异步检查指定键是否存在
/// </summary>
/// <param name="key">要检查的键</param>
/// <returns>如果键存在则返回true否则返回false</returns>
public Task<bool> ExistsAsync(string key)
=> inner.ExistsAsync(Key(key));
/// <summary>
/// 读取指定键的值
/// </summary>
/// <typeparam name="T">要读取的值的类型</typeparam>
/// <param name="key">要读取的键</param>
/// <returns>键对应的值</returns>
public T Read<T>(string key)
=> inner.Read<T>(Key(key));
/// <summary>
/// 读取指定键的值,如果键不存在则返回默认值
/// </summary>
/// <typeparam name="T">要读取的值的类型</typeparam>
/// <param name="key">要读取的键</param>
/// <param name="defaultValue">当键不存在时返回的默认值</param>
/// <returns>键对应的值或默认值</returns>
public T Read<T>(string key, T defaultValue)
=> inner.Read(Key(key), defaultValue);
/// <summary>
/// 异步读取指定键的值
/// </summary>
/// <typeparam name="T">要读取的值的类型</typeparam>
/// <param name="key">要读取的键</param>
/// <returns>键对应的值的任务</returns>
public Task<T> ReadAsync<T>(string key)
=> inner.ReadAsync<T>(Key(key));
/// <summary>
/// 写入指定键值对
/// </summary>
/// <typeparam name="T">要写入的值的类型</typeparam>
/// <param name="key">要写入的键</param>
/// <param name="value">要写入的值</param>
public void Write<T>(string key, T value)
=> inner.Write(Key(key), value);
/// <summary>
/// 异步写入指定键值对
/// </summary>
/// <typeparam name="T">要写入的值的类型</typeparam>
/// <param name="key">要写入的键</param>
/// <param name="value">要写入的值</param>
public Task WriteAsync<T>(string key, T value)
=> inner.WriteAsync(Key(key), value);
/// <summary>
/// 删除指定键
/// </summary>
/// <param name="key">要删除的键</param>
public void Delete(string key)
=> inner.Delete(Key(key));
/// <summary>
/// 为给定的键添加前缀
/// </summary>
/// <param name="key">原始键</param>
/// <returns>添加前缀后的键</returns>
private string Key(string key)
=> string.IsNullOrEmpty(prefix)
? key
: $"{prefix}/{key}";
/// <summary>
/// 创建一个新的作用域存储实例
/// </summary>
/// <param name="scope">新的作用域名称</param>
/// <returns>新的作用域存储实例</returns>
public IStorage Scope(string scope)
=> new ScopedStorage(inner, Key(scope));
}