GFramework/GFramework.Core/Configuration/ConfigurationManager.cs
gewuyou ff553977e3 chore(license): 补齐 Apache-2.0 文件头治理
- 新增许可证文件头检查与修复脚本

- 补充维护者手动修复 PR 工作流和 CI 校验

- 更新贡献指南中的文件头说明

- 补齐仓库维护源码和配置文件的许可证声明
2026-05-03 19:39:49 +08:00

342 lines
9.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
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!;
}
}
}