diff --git a/GFramework.Core.Abstractions/storage/IStorage.cs b/GFramework.Core.Abstractions/storage/IStorage.cs new file mode 100644 index 0000000..a39105b --- /dev/null +++ b/GFramework.Core.Abstractions/storage/IStorage.cs @@ -0,0 +1,72 @@ +using System.Threading.Tasks; +using GFramework.Core.Abstractions.utility; + +namespace GFramework.Core.Abstractions.storage; + +/// +/// 存储接口,提供同步和异步的数据存储操作功能 +/// +public interface IStorage : IUtility +{ + /// + /// 检查指定键的存储项是否存在 + /// + /// 要检查的键 + /// 如果键存在则返回true,否则返回false + bool Exists(string key); + + /// + /// 异步检查指定键的存储项是否存在 + /// + /// 要检查的键 + /// 如果键存在则返回true,否则返回false + Task ExistsAsync(string key); + + /// + /// 读取指定键的值 + /// + /// 要读取的值的类型 + /// 要读取的键 + /// 指定键对应的值 + T Read(string key); + + /// + /// 读取指定键的值,如果键不存在则返回默认值 + /// + /// 要读取的值的类型 + /// 要读取的键 + /// 当键不存在时返回的默认值 + /// 指定键对应的值,如果键不存在则返回默认值 + T Read(string key, T defaultValue); + + /// + /// 异步读取指定键的值 + /// + /// 要读取的值的类型 + /// 要读取的键 + /// 指定键对应的值 + Task ReadAsync(string key); + + /// + /// 将值写入指定键 + /// + /// 要写入的值的类型 + /// 要写入的键 + /// 要写入的值 + void Write(string key, T value); + + /// + /// 异步将值写入指定键 + /// + /// 要写入的值的类型 + /// 要写入的键 + /// 要写入的值 + /// 异步操作任务 + Task WriteAsync(string key, T value); + + /// + /// 删除指定键的存储项 + /// + /// 要删除的键 + void Delete(string key); +} \ No newline at end of file diff --git a/GFramework.Game.Abstractions/serializer/ISerializer.cs b/GFramework.Game.Abstractions/serializer/ISerializer.cs new file mode 100644 index 0000000..a38b556 --- /dev/null +++ b/GFramework.Game.Abstractions/serializer/ISerializer.cs @@ -0,0 +1,23 @@ +namespace GFramework.Game.Abstractions.serializer; + +/// +/// 定义序列化器接口,提供对象序列化和反序列化的通用方法 +/// +public interface ISerializer +{ + /// + /// 将指定的对象序列化为字符串 + /// + /// 要序列化的对象类型 + /// 要序列化的对象实例 + /// 序列化后的字符串表示 + string Serialize(T value); + + /// + /// 将字符串数据反序列化为指定类型的对象 + /// + /// 要反序列化的目标对象类型 + /// 包含序列化数据的字符串 + /// 反序列化后的对象实例 + T Deserialize(string data); +} \ No newline at end of file diff --git a/GFramework.Game/GFramework.Game.csproj b/GFramework.Game/GFramework.Game.csproj index 9305dcc..13983d2 100644 --- a/GFramework.Game/GFramework.Game.csproj +++ b/GFramework.Game/GFramework.Game.csproj @@ -10,4 +10,7 @@ + + + diff --git a/GFramework.Game/serializer/JsonSerializer.cs b/GFramework.Game/serializer/JsonSerializer.cs new file mode 100644 index 0000000..fa4de37 --- /dev/null +++ b/GFramework.Game/serializer/JsonSerializer.cs @@ -0,0 +1,29 @@ +using GFramework.Game.Abstractions.serializer; +using Newtonsoft.Json; + +namespace GFramework.Game.serializer; + +/// +/// JSON序列化器实现类,用于将对象序列化为JSON字符串或将JSON字符串反序列化为对象 +/// +public sealed class JsonSerializer : ISerializer +{ + /// + /// 将指定的对象序列化为JSON字符串 + /// + /// 要序列化的对象类型 + /// 要序列化的对象实例 + /// 序列化后的JSON字符串 + public string Serialize(T value) + => JsonConvert.SerializeObject(value); + + /// + /// 将JSON字符串反序列化为指定类型的对象 + /// + /// 要反序列化的对象类型 + /// 包含JSON数据的字符串 + /// 反序列化后的对象实例 + /// 当无法反序列化数据时抛出 + public T Deserialize(string data) + => JsonConvert.DeserializeObject(data) ?? throw new ArgumentException("Cannot deserialize data"); +} \ No newline at end of file diff --git a/GFramework.Game/storage/FileStorage.cs b/GFramework.Game/storage/FileStorage.cs new file mode 100644 index 0000000..e5c1d63 --- /dev/null +++ b/GFramework.Game/storage/FileStorage.cs @@ -0,0 +1,170 @@ +using System.Text; +using GFramework.Core.Abstractions.storage; +using GFramework.Game.Abstractions.serializer; + +namespace GFramework.Game.storage; + +/// +/// 基于文件系统的存储实现,实现了IStorage接口 +/// +public sealed class FileStorage : IStorage +{ + private readonly string _extension; + 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); + if (File.Exists(path)) + File.Delete(path); + } + + #endregion + + #region Helpers + + /// + /// 将存储键转换为文件路径 + /// + /// 存储键 + /// 对应的文件路径 + private string ToPath(string key) + { + // 防止非法路径 + key = Path.GetInvalidFileNameChars().Aggregate(key, (current, c) => current.Replace(c, '_')); + return Path.Combine(_rootPath, $"{key}{_extension}"); + } + + #endregion + + #region Exists + + /// + /// 检查指定键的存储项是否存在 + /// + /// 存储键 + /// 存在返回true,否则返回false + public bool Exists(string key) + => File.Exists(ToPath(key)); + + /// + /// 异步检查指定键的存储项是否存在 + /// + /// 存储键 + /// 存在返回true,否则返回false + public Task ExistsAsync(string key) + => Task.FromResult(Exists(key)); + + #endregion + + #region Read + + /// + /// 读取指定键的存储项 + /// + /// 要反序列化的类型 + /// 存储键 + /// 反序列化后的对象 + /// 当指定键不存在时抛出 + public T Read(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(content); + } + + /// + /// 读取指定键的存储项,如果不存在则返回默认值 + /// + /// 要反序列化的类型 + /// 存储键 + /// 默认值 + /// 存在则返回反序列化后的对象,否则返回默认值 + public T Read(string key, T defaultValue) + { + var path = ToPath(key); + 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); + + if (!File.Exists(path)) + throw new FileNotFoundException($"Storage key not found: {key}", path); + + var content = await File.ReadAllTextAsync(path, Encoding.UTF8); + return _serializer.Deserialize(content); + } + + #endregion + + #region Write + + /// + /// 写入指定键的存储项 + /// + /// 要序列化的类型 + /// 存储键 + /// 要存储的值 + public void Write(string key, T value) + { + var path = ToPath(key); + var content = _serializer.Serialize(value); + + File.WriteAllText(path, content, Encoding.UTF8); + } + + /// + /// 异步写入指定键的存储项 + /// + /// 要序列化的类型 + /// 存储键 + /// 要存储的值 + public async Task WriteAsync(string key, T value) + { + var path = ToPath(key); + var content = _serializer.Serialize(value); + + await File.WriteAllTextAsync(path, content, Encoding.UTF8); + } + + #endregion +} \ No newline at end of file diff --git a/GFramework.Game/storage/ScopedStorage.cs b/GFramework.Game/storage/ScopedStorage.cs new file mode 100644 index 0000000..ba85f19 --- /dev/null +++ b/GFramework.Game/storage/ScopedStorage.cs @@ -0,0 +1,98 @@ +using GFramework.Core.Abstractions.storage; + +namespace GFramework.Game.storage; + +/// +/// 提供带有作用域前缀的存储包装器,将所有键都加上指定的前缀 +/// +/// 内部的实际存储实现 +/// 用于所有键的前缀字符串 +public sealed class ScopedStorage(IStorage inner, string prefix) : IStorage +{ + /// + /// 检查指定键是否存在 + /// + /// 要检查的键 + /// 如果键存在则返回true,否则返回false + public bool Exists(string key) + => inner.Exists(Key(key)); + + /// + /// 异步检查指定键是否存在 + /// + /// 要检查的键 + /// 如果键存在则返回true,否则返回false + public Task ExistsAsync(string key) + => inner.ExistsAsync(Key(key)); + + /// + /// 读取指定键的值 + /// + /// 要读取的值的类型 + /// 要读取的键 + /// 键对应的值 + public T Read(string key) + => inner.Read(Key(key)); + + /// + /// 读取指定键的值,如果键不存在则返回默认值 + /// + /// 要读取的值的类型 + /// 要读取的键 + /// 当键不存在时返回的默认值 + /// 键对应的值或默认值 + public T Read(string key, T defaultValue) + => inner.Read(Key(key), defaultValue); + + /// + /// 异步读取指定键的值 + /// + /// 要读取的值的类型 + /// 要读取的键 + /// 键对应的值的任务 + public Task ReadAsync(string key) + => inner.ReadAsync(Key(key)); + + /// + /// 写入指定键值对 + /// + /// 要写入的值的类型 + /// 要写入的键 + /// 要写入的值 + public void Write(string key, T value) + => inner.Write(Key(key), value); + + /// + /// 异步写入指定键值对 + /// + /// 要写入的值的类型 + /// 要写入的键 + /// 要写入的值 + public Task WriteAsync(string key, T value) + => inner.WriteAsync(Key(key), value); + + /// + /// 删除指定键 + /// + /// 要删除的键 + public void Delete(string key) + => inner.Delete(Key(key)); + + /// + /// 为给定的键添加前缀 + /// + /// 原始键 + /// 添加前缀后的键 + private string Key(string key) + => string.IsNullOrEmpty(prefix) + ? key + : $"{prefix}/{key}"; + + /// + /// 创建一个新的作用域存储实例 + /// + /// 新的作用域名称 + /// 新的作用域存储实例 + public IStorage Scope(string scope) + => new ScopedStorage(inner, Key(scope)); +} \ No newline at end of file