diff --git a/GFramework.Game.Abstractions/data/DataRepositoryOptions.cs b/GFramework.Game.Abstractions/data/DataRepositoryOptions.cs
index f8134e2..b12cd48 100644
--- a/GFramework.Game.Abstractions/data/DataRepositoryOptions.cs
+++ b/GFramework.Game.Abstractions/data/DataRepositoryOptions.cs
@@ -26,7 +26,7 @@ public class DataRepositoryOptions
///
/// 键名前缀(如 "Game",生成的键为 "Game_SettingsData")
///
- public string KeyPrefix { get; set; } = "Data";
+ public string KeyPrefix { get; set; } = "";
///
/// 是否在保存时自动备份
diff --git a/GFramework.Game/data/UnifiedSettingsRepository.cs b/GFramework.Game/data/UnifiedSettingsRepository.cs
new file mode 100644
index 0000000..2170c32
--- /dev/null
+++ b/GFramework.Game/data/UnifiedSettingsRepository.cs
@@ -0,0 +1,239 @@
+// Copyright (c) 2026 GeWuYou
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using GFramework.Core.Abstractions.serializer;
+using GFramework.Core.Abstractions.storage;
+using GFramework.Core.extensions;
+using GFramework.Core.utility;
+using GFramework.Game.Abstractions.data;
+using GFramework.Game.Abstractions.data.events;
+
+namespace GFramework.Game.data;
+
+///
+/// 使用单一文件存储所有设置数据的仓库实现
+///
+public sealed class UnifiedSettingsRepository(
+ IStorage? storage,
+ IRuntimeTypeSerializer? serializer,
+ DataRepositoryOptions? options = null,
+ string fileName = "settings.json")
+ : AbstractContextUtility, IDataRepository
+{
+ private readonly Dictionary _cache = new();
+ private readonly SemaphoreSlim _lock = new(1, 1);
+ private readonly DataRepositoryOptions _options = options ?? new DataRepositoryOptions();
+ private bool _loaded;
+ private IRuntimeTypeSerializer? _serializer = serializer;
+ private IStorage? _storage = storage;
+
+ private IStorage Storage =>
+ _storage ?? throw new InvalidOperationException("IStorage not initialized.");
+
+ private IRuntimeTypeSerializer Serializer =>
+ _serializer ?? throw new InvalidOperationException("ISerializer not initialized.");
+
+ // =========================
+ // IDataRepository
+ // =========================
+
+ ///
+ /// 异步加载指定类型的数据
+ ///
+ /// 要加载的数据类型,必须继承自IData接口并具有无参构造函数
+ /// 加载的数据实例
+ public async Task LoadAsync() where T : class, IData, new()
+ {
+ await EnsureLoadedAsync();
+
+ var key = GetTypeKey(typeof(T));
+
+ var result = _cache.TryGetValue(key, out var json) ? Serializer.Deserialize(json) : new T();
+
+ if (_options.EnableEvents)
+ this.SendEvent(new DataLoadedEvent(result));
+
+ return result;
+ }
+
+ ///
+ /// 异步加载指定类型的数据(通过Type参数)
+ ///
+ /// 要加载的数据类型
+ /// 加载的数据实例
+ /// 当类型不符合要求时抛出异常
+ public async Task LoadAsync(Type type)
+ {
+ if (!typeof(IData).IsAssignableFrom(type))
+ throw new ArgumentException($"{type.Name} does not implement IData");
+
+ if (!type.IsClass || type.GetConstructor(Type.EmptyTypes) == null)
+ throw new ArgumentException($"{type.Name} must have parameterless ctor");
+
+ await EnsureLoadedAsync();
+
+ var key = GetTypeKey(type);
+
+ IData result;
+ if (_cache.TryGetValue(key, out var json))
+ {
+ result = (IData)Serializer.Deserialize(json, type);
+ }
+ else
+ {
+ result = (IData)Activator.CreateInstance(type)!;
+ }
+
+ if (_options.EnableEvents)
+ this.SendEvent(new DataLoadedEvent(result));
+
+ return result;
+ }
+
+ ///
+ /// 异步保存数据到存储
+ ///
+ /// 要保存的数据类型
+ /// 要保存的数据实例
+ public async Task SaveAsync(T data) where T : class, IData
+ {
+ await EnsureLoadedAsync();
+
+ var key = GetTypeKey(typeof(T));
+ _cache[key] = Serializer.Serialize(data);
+
+ await SaveUnifiedFileAsync();
+
+ if (_options.EnableEvents)
+ this.SendEvent(new DataSavedEvent(data));
+ }
+
+ ///
+ /// 异步批量保存多个数据实例
+ ///
+ /// 要保存的数据实例集合
+ public async Task SaveAllAsync(IEnumerable dataList)
+ {
+ await EnsureLoadedAsync();
+
+ var list = dataList.ToList();
+ foreach (var data in list)
+ {
+ var key = GetTypeKey(data.GetType());
+ _cache[key] = Serializer.Serialize(data);
+ }
+
+ await SaveUnifiedFileAsync();
+
+ if (_options.EnableEvents)
+ this.SendEvent(new DataBatchSavedEvent(list));
+ }
+
+ ///
+ /// 检查指定类型的数据是否存在
+ ///
+ /// 要检查的数据类型
+ /// 如果存在返回true,否则返回false
+ public async Task ExistsAsync() where T : class, IData
+ {
+ await EnsureLoadedAsync();
+ return _cache.ContainsKey(GetTypeKey(typeof(T)));
+ }
+
+ ///
+ /// 删除指定类型的数据
+ ///
+ /// 要删除的数据类型
+ public async Task DeleteAsync() where T : class, IData
+ {
+ await EnsureLoadedAsync();
+
+ _cache.Remove(GetTypeKey(typeof(T)));
+ await SaveUnifiedFileAsync();
+
+ if (_options.EnableEvents)
+ this.SendEvent(new DataDeletedEvent(typeof(T)));
+ }
+
+ protected override void OnInit()
+ {
+ _storage ??= this.GetUtility()!;
+ _serializer ??= this.GetUtility()!;
+ }
+
+ // =========================
+ // Internals
+ // =========================
+
+ ///
+ /// 确保数据已从存储中加载到缓存
+ ///
+ private async Task EnsureLoadedAsync()
+ {
+ if (_loaded) return;
+
+ await _lock.WaitAsync();
+ try
+ {
+ if (_loaded) return;
+
+ if (await Storage.ExistsAsync(GetUnifiedKey()))
+ {
+ var data = await Storage.ReadAsync>(GetUnifiedKey());
+ _cache.Clear();
+ foreach (var (k, v) in data)
+ _cache[k] = v;
+ }
+
+ _loaded = true;
+ }
+ finally
+ {
+ _lock.Release();
+ }
+ }
+
+ ///
+ /// 将缓存中的所有数据保存到统一文件
+ ///
+ private async Task SaveUnifiedFileAsync()
+ {
+ await _lock.WaitAsync();
+ try
+ {
+ await Storage.WriteAsync(GetUnifiedKey(), _cache);
+ }
+ finally
+ {
+ _lock.Release();
+ }
+ }
+
+ ///
+ /// 获取统一文件的存储键名
+ ///
+ /// 完整的存储键名
+ private string GetUnifiedKey()
+ {
+ var name = string.IsNullOrEmpty(_options.KeyPrefix) ? fileName : $"{_options.KeyPrefix}_{fileName}";
+ return string.IsNullOrEmpty(_options.BasePath) ? name : $"{_options.BasePath.TrimEnd('/')}/{name}";
+ }
+
+ ///
+ /// 获取类型的唯一标识键
+ ///
+ /// 要获取键的类型
+ /// 类型的全名作为键
+ private static string GetTypeKey(Type type)
+ => type.FullName!; // ⚠️ 刻意不用 AssemblyQualifiedName
+}
\ No newline at end of file
diff --git a/GFramework.Game/serializer/JsonSerializer.cs b/GFramework.Game/serializer/JsonSerializer.cs
index 925ce31..7ded055 100644
--- a/GFramework.Game/serializer/JsonSerializer.cs
+++ b/GFramework.Game/serializer/JsonSerializer.cs
@@ -6,28 +6,46 @@ namespace GFramework.Game.serializer;
///
/// JSON序列化器实现类,用于将对象序列化为JSON字符串或将JSON字符串反序列化为对象
///
-public sealed class JsonSerializer : ISerializer
+public sealed class JsonSerializer
+ : IRuntimeTypeSerializer
{
///
- /// 将指定的对象序列化为JSON字符串
+ /// 将指定类型的对象序列化为JSON字符串
///
/// 要序列化的对象类型
/// 要序列化的对象实例
/// 序列化后的JSON字符串
public string Serialize(T value)
- {
- return JsonConvert.SerializeObject(value);
- }
+ => JsonConvert.SerializeObject(value);
///
/// 将JSON字符串反序列化为指定类型的对象
///
- /// 要反序列化的对象类型
- /// 包含JSON数据的字符串
+ /// 要反序列化的目标类型
+ /// 要反序列化的JSON字符串数据
/// 反序列化后的对象实例
/// 当无法反序列化数据时抛出
public T Deserialize(string data)
- {
- return JsonConvert.DeserializeObject(data) ?? throw new ArgumentException("Cannot deserialize data");
- }
+ => JsonConvert.DeserializeObject(data)
+ ?? throw new ArgumentException("Cannot deserialize data");
+
+ ///
+ /// 将对象序列化为JSON字符串(使用运行时类型)
+ ///
+ /// 要序列化的对象实例
+ /// 对象的运行时类型
+ /// 序列化后的JSON字符串
+ public string Serialize(object obj, Type type)
+ => JsonConvert.SerializeObject(obj, type, null);
+
+ ///
+ /// 将JSON字符串反序列化为指定类型的对象(使用运行时类型)
+ ///
+ /// 要反序列化的JSON字符串数据
+ /// 反序列化目标类型
+ /// 反序列化后的对象实例
+ /// 当无法反序列化到指定类型时抛出
+ public object Deserialize(string data, Type type)
+ => JsonConvert.DeserializeObject(data, type)
+ ?? throw new ArgumentException($"Cannot deserialize to {type.Name}");
}
\ No newline at end of file