mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-07 17:21:16 +08:00
- 修复 Core 与 Cqrs 中资源、日志、配置缓存的 MA0158 专用锁警告 - 修复 SaveRepository 与 SceneRouterBase 的残留分析器警告 - 更新 analyzer-warning-reduction 跟踪文档与最新构建验证结果
339 lines
9.3 KiB
C#
339 lines
9.3 KiB
C#
using System.Collections.Concurrent;
|
||
using System.Globalization;
|
||
using System.IO;
|
||
using System.Text.Json;
|
||
using GFramework.Core.Abstractions.Configuration;
|
||
using GFramework.Core.Abstractions.Events;
|
||
using GFramework.Core.Abstractions.Logging;
|
||
using GFramework.Core.Logging;
|
||
|
||
namespace GFramework.Core.Configuration;
|
||
|
||
/// <summary>
|
||
/// 配置管理器实现,提供线程安全的配置存储和访问
|
||
/// 线程安全:所有公共方法都是线程安全的
|
||
/// </summary>
|
||
public class ConfigurationManager : IConfigurationManager
|
||
{
|
||
/// <summary>
|
||
/// Key 参数验证错误消息常量
|
||
/// </summary>
|
||
private const string KeyCannotBeNullOrEmptyMessage = "Key cannot be null or whitespace.";
|
||
|
||
/// <summary>
|
||
/// Path 参数验证错误消息常量
|
||
/// </summary>
|
||
private const string PathCannotBeNullOrEmptyMessage = "Path cannot be null or whitespace.";
|
||
|
||
/// <summary>
|
||
/// JSON 参数验证错误消息常量
|
||
/// </summary>
|
||
private const string JsonCannotBeNullOrEmptyMessage = "JSON cannot be null or whitespace.";
|
||
|
||
/// <summary>
|
||
/// 配置存储字典(线程安全)
|
||
/// </summary>
|
||
private readonly ConcurrentDictionary<string, object> _configs = new(StringComparer.Ordinal);
|
||
|
||
private readonly ILogger _logger = LoggerFactoryResolver.Provider.CreateLogger(nameof(ConfigurationManager));
|
||
|
||
/// <summary>
|
||
/// 用于保护监听器列表的锁
|
||
/// </summary>
|
||
#if NET9_0_OR_GREATER
|
||
// net9.0 及以上目标使用专用 Lock,以满足分析器对专用同步原语的建议。
|
||
private readonly System.Threading.Lock _watcherLock = new();
|
||
#else
|
||
// net8.0 目标仍回退到 object 锁,以保持多目标编译兼容性。
|
||
private readonly object _watcherLock = new();
|
||
#endif
|
||
|
||
/// <summary>
|
||
/// 配置监听器字典(线程安全)
|
||
/// 键:配置键,值:监听器列表
|
||
/// </summary>
|
||
private readonly ConcurrentDictionary<string, List<Delegate>> _watchers = new(StringComparer.Ordinal);
|
||
|
||
/// <summary>
|
||
/// 获取配置数量
|
||
/// </summary>
|
||
public int Count => _configs.Count;
|
||
|
||
/// <summary>
|
||
/// 获取指定键的配置值
|
||
/// </summary>
|
||
public T? GetConfig<T>(string key)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(key))
|
||
throw new ArgumentException(KeyCannotBeNullOrEmptyMessage, nameof(key));
|
||
|
||
if (_configs.TryGetValue(key, out var value))
|
||
{
|
||
return ConvertValue<T>(value);
|
||
}
|
||
|
||
return default;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取指定键的配置值,如果不存在则返回默认值
|
||
/// </summary>
|
||
public T GetConfig<T>(string key, T defaultValue)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(key))
|
||
throw new ArgumentException(KeyCannotBeNullOrEmptyMessage, nameof(key));
|
||
|
||
if (_configs.TryGetValue(key, out var value))
|
||
{
|
||
return ConvertValue<T>(value);
|
||
}
|
||
|
||
return defaultValue;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置指定键的配置值
|
||
/// </summary>
|
||
public void SetConfig<T>(string key, T value)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(key))
|
||
throw new ArgumentException(KeyCannotBeNullOrEmptyMessage, nameof(key));
|
||
|
||
// 先获取旧值,以便正确检测变更
|
||
_configs.TryGetValue(key, out var oldValue);
|
||
|
||
// 更新配置值
|
||
_configs[key] = value!;
|
||
|
||
// 只有在值真正变化时才触发监听器
|
||
if (!EqualityComparer<object>.Default.Equals(oldValue, value))
|
||
{
|
||
NotifyWatchers(key, value);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查指定键的配置是否存在
|
||
/// </summary>
|
||
public bool HasConfig(string key)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(key))
|
||
throw new ArgumentException(KeyCannotBeNullOrEmptyMessage, nameof(key));
|
||
|
||
return _configs.ContainsKey(key);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 移除指定键的配置
|
||
/// </summary>
|
||
public bool RemoveConfig(string key)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(key))
|
||
throw new ArgumentException(KeyCannotBeNullOrEmptyMessage, nameof(key));
|
||
|
||
var removed = _configs.TryRemove(key, out _);
|
||
|
||
if (removed)
|
||
{
|
||
// 移除该键的所有监听器(使用锁保护)
|
||
lock (_watcherLock)
|
||
{
|
||
_watchers.TryRemove(key, out _);
|
||
}
|
||
}
|
||
|
||
return removed;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清空所有配置
|
||
/// </summary>
|
||
public void Clear()
|
||
{
|
||
_configs.Clear();
|
||
|
||
// 清空监听器(使用锁保护)
|
||
lock (_watcherLock)
|
||
{
|
||
_watchers.Clear();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 监听指定键的配置变化
|
||
/// </summary>
|
||
public IUnRegister WatchConfig<T>(string key, Action<T> onChange)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(key))
|
||
throw new ArgumentException(KeyCannotBeNullOrEmptyMessage, nameof(key));
|
||
|
||
if (onChange == null)
|
||
throw new ArgumentNullException(nameof(onChange));
|
||
|
||
lock (_watcherLock)
|
||
{
|
||
if (!_watchers.TryGetValue(key, out var watchers))
|
||
{
|
||
watchers = new List<Delegate>();
|
||
_watchers[key] = watchers;
|
||
}
|
||
|
||
watchers.Add(onChange);
|
||
}
|
||
|
||
return new ConfigWatcherUnRegister(() => UnwatchConfig(key, onChange));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从 JSON 字符串加载配置
|
||
/// </summary>
|
||
public void LoadFromJson(string json)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(json))
|
||
throw new ArgumentException(JsonCannotBeNullOrEmptyMessage, nameof(json));
|
||
|
||
var dict = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(json);
|
||
if (dict == null)
|
||
return;
|
||
|
||
foreach (var kvp in dict)
|
||
{
|
||
_configs[kvp.Key] = kvp.Value;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将配置保存为 JSON 字符串
|
||
/// </summary>
|
||
public string SaveToJson()
|
||
{
|
||
var dict = _configs.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, StringComparer.Ordinal);
|
||
return JsonSerializer.Serialize(dict, new JsonSerializerOptions
|
||
{
|
||
WriteIndented = true
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从文件加载配置
|
||
/// </summary>
|
||
public void LoadFromFile(string path)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(path))
|
||
throw new ArgumentException(PathCannotBeNullOrEmptyMessage, nameof(path));
|
||
|
||
if (!File.Exists(path))
|
||
throw new FileNotFoundException($"Configuration file not found: {path}");
|
||
|
||
var json = File.ReadAllText(path);
|
||
LoadFromJson(json);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将配置保存到文件
|
||
/// </summary>
|
||
public void SaveToFile(string path)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(path))
|
||
throw new ArgumentException(PathCannotBeNullOrEmptyMessage, nameof(path));
|
||
|
||
var directory = Path.GetDirectoryName(path);
|
||
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
|
||
{
|
||
Directory.CreateDirectory(directory);
|
||
}
|
||
|
||
var json = SaveToJson();
|
||
File.WriteAllText(path, json);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取所有配置键
|
||
/// </summary>
|
||
public IEnumerable<string> GetAllKeys()
|
||
{
|
||
return _configs.Keys.ToList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 取消监听指定键的配置变化
|
||
/// </summary>
|
||
private void UnwatchConfig<T>(string key, Action<T> onChange)
|
||
{
|
||
lock (_watcherLock)
|
||
{
|
||
if (_watchers.TryGetValue(key, out var watchers))
|
||
{
|
||
watchers.Remove(onChange);
|
||
|
||
if (watchers.Count == 0)
|
||
{
|
||
_watchers.TryRemove(key, out _);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 通知监听器配置已变化
|
||
/// </summary>
|
||
private void NotifyWatchers<T>(string key, T newValue)
|
||
{
|
||
List<Delegate>? watchersCopy = null;
|
||
|
||
lock (_watcherLock)
|
||
{
|
||
if (_watchers.TryGetValue(key, out var watchers))
|
||
{
|
||
watchersCopy = new List<Delegate>(watchers);
|
||
}
|
||
}
|
||
|
||
if (watchersCopy == null)
|
||
return;
|
||
|
||
foreach (var watcher in watchersCopy)
|
||
{
|
||
try
|
||
{
|
||
if (watcher is Action<T> typedWatcher)
|
||
{
|
||
typedWatcher(newValue);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 防止监听器异常影响其他监听器
|
||
_logger.Error($"Error in config watcher for key '{key}'", ex);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 转换配置值到目标类型
|
||
/// </summary>
|
||
private static T ConvertValue<T>(object value)
|
||
{
|
||
if (value is T typedValue)
|
||
{
|
||
return typedValue;
|
||
}
|
||
|
||
// 处理 JsonElement 类型
|
||
if (value is JsonElement jsonElement)
|
||
{
|
||
return JsonSerializer.Deserialize<T>(jsonElement.GetRawText())!;
|
||
}
|
||
|
||
// 尝试类型转换
|
||
try
|
||
{
|
||
return (T)Convert.ChangeType(value, typeof(T), CultureInfo.InvariantCulture);
|
||
}
|
||
catch
|
||
{
|
||
return default!;
|
||
}
|
||
}
|
||
}
|