Files
ThingsGateway/src/Admin/ThingsGateway.NewLife.X/Configuration/IConfigProvider.cs
2025-07-25 20:20:35 +08:00

389 lines
14 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.

using ThingsGateway.NewLife.Reflection;
namespace ThingsGateway.NewLife.Configuration;
/// <summary>配置提供者</summary>
/// <remarks>
/// 建立树状配置数据体系以分布式配置中心为核心支持基于key的索引读写也支持Load/Save/Bind的实体模型转换。
/// key索引支持冒号分隔的多层结构在配置中心中不同命名空间使用不同提供者实例在文件配置中不同文件使用不同提供者实例。
///
/// 一个配置类,支持从不同持久化提供者读取,可根据需要选择配置持久化策略。
/// 例如小系统采用ini/xml/json文件配置分布式系统采用配置中心。
///
/// 可通过实现IConfigMapping接口来自定义映射配置到模型实例。
/// </remarks>
public interface IConfigProvider
{
/// <summary>名称</summary>
String Name { get; set; }
/// <summary>根元素</summary>
IConfigSection Root { get; set; }
/// <summary>所有键</summary>
ICollection<String> Keys { get; }
/// <summary>是否新的配置文件</summary>
Boolean IsNew { get; set; }
/// <summary>获取 或 设置 配置值</summary>
/// <param name="key">配置名,支持冒号分隔的多级名称</param>
/// <returns></returns>
String? this[String key] { get; set; }
/// <summary>查找配置项。可得到子级和配置</summary>
/// <param name="key">配置名</param>
/// <returns></returns>
IConfigSection? GetSection(String key);
/// <summary>配置改变事件。执行了某些动作,可能导致配置数据发生改变时触发</summary>
event EventHandler Changed;
/// <summary>返回获取配置的委托</summary>
GetConfigCallback GetConfig { get; }
/// <summary>从数据源加载数据到配置树</summary>
Boolean LoadAll();
/// <summary>保存配置树到数据源</summary>
Boolean SaveAll();
/// <summary>加载配置到模型</summary>
/// <typeparam name="T">模型。可通过实现IConfigMapping接口来自定义映射配置到模型实例</typeparam>
/// <param name="path">路径。配置树位置,配置中心等多对象混合使用时</param>
/// <returns></returns>
T? Load<T>(String? path = null) where T : new();
/// <summary>保存模型实例</summary>
/// <typeparam name="T">模型</typeparam>
/// <param name="model">模型实例</param>
/// <param name="path">路径。配置树位置,配置中心等多对象混合使用时</param>
Boolean Save<T>(T model, String? path = null);
/// <summary>绑定模型,使能热更新,配置存储数据改变时同步修改模型属性</summary>
/// <typeparam name="T">模型。可通过实现IConfigMapping接口来自定义映射配置到模型实例</typeparam>
/// <param name="model">模型实例</param>
/// <param name="autoReload">是否自动更新。默认true</param>
/// <param name="path">命名空间。配置树位置,配置中心等多对象混合使用时</param>
void Bind<T>(T model, Boolean autoReload = true, String? path = null);
/// <summary>绑定模型,使能热更新,配置存储数据改变时同步修改模型属性</summary>
/// <typeparam name="T">模型。可通过实现IConfigMapping接口来自定义映射配置到模型实例</typeparam>
/// <param name="model">模型实例</param>
/// <param name="path">命名空间。配置树位置,配置中心等多对象混合使用时</param>
/// <param name="onChange">配置改变时执行的委托</param>
void Bind<T>(T model, String path, Action<IConfigSection> onChange);
}
/// <summary>配置提供者基类</summary>
/// <remarks>
/// 同时也是基于Items字典的内存配置提供者。
/// </remarks>
public abstract class ConfigProvider : DisposeBase, IConfigProvider
{
#region
/// <summary>名称</summary>
public String Name { get; set; }
/// <summary>根元素</summary>
public virtual IConfigSection Root { get; set; } = new ConfigSection { Childs = new List<IConfigSection>() };
/// <summary>所有键</summary>
public virtual ICollection<String> Keys
{
get
{
//Root?.Childs?.Select(e => e.Key).ToList();
var list = new List<String>();
var childs = Root?.Childs;
if (childs == null) return list;
foreach (var item in childs)
{
if (item.Key != null) list.Add(item.Key);
}
return list;
}
}
/// <summary>已使用的键</summary>
public ICollection<String> UsedKeys { get; } = new List<String>();
/// <summary>缺失的键</summary>
public ICollection<String> MissedKeys { get; } = new List<String>();
/// <summary>返回获取配置的委托</summary>
public virtual GetConfigCallback GetConfig => key => Find(key, false)?.Value;
/// <summary>配置改变事件。执行了某些动作,可能导致配置数据发生改变时触发</summary>
public event EventHandler? Changed;
/// <summary>是否新的配置文件</summary>
public Boolean IsNew { get; set; }
#endregion
#region
/// <summary>构造函数</summary>
public ConfigProvider() => Name = GetType().Name.TrimEnd("ConfigProvider");
#endregion
#region
/// <summary>获取 或 设置 配置值</summary>
/// <param name="key">键</param>
/// <returns></returns>
public virtual String? this[String key]
{
get { EnsureLoad(); return Find(key, false)?.Value; }
set
{
var section = Find(key, true);
if (section != null) section.Value = value;
}
}
/// <summary>查找配置项。可得到子级和配置</summary>
/// <param name="key"></param>
/// <returns></returns>
public virtual IConfigSection? GetSection(String key) => Find(key, false);
/// <summary>查找配置项,可指定是否创建</summary>
/// <remarks>配置提供者可以重载该方法以实现增强功能。例如星尘配置从注册中心读取数据</remarks>
/// <param name="key"></param>
/// <param name="createOnMiss"></param>
/// <returns></returns>
protected virtual IConfigSection? Find(String key, Boolean createOnMiss)
{
UseKey(key);
EnsureLoad();
var sec = Root.Find(key, createOnMiss);
if (sec == null) MissKey(key);
return sec;
}
internal void UseKey(String key)
{
if (!key.IsNullOrEmpty() && !UsedKeys.Contains(key)) UsedKeys.Add(key);
}
internal void MissKey(String key)
{
if (!key.IsNullOrEmpty() && !MissedKeys.Contains(key)) MissedKeys.Add(key);
}
/// <summary>初始化提供者</summary>
/// <param name="value"></param>
public virtual void Init(String value) { }
#endregion
protected object lockThis = new();
#region /
/// <summary>从数据源加载数据到配置树</summary>
public virtual Boolean LoadAll() => true;
private Boolean _Loaded;
private void EnsureLoad()
{
if (_Loaded) return;
lock (lockThis)
{
if (_Loaded) return;
LoadAll();
_Loaded = true;
}
}
/// <summary>加载配置到模型</summary>
/// <typeparam name="T">模型。可通过实现IConfigMapping接口来自定义映射配置到模型实例</typeparam>
/// <param name="path">路径。配置树位置,配置中心等多对象混合使用时</param>
/// <returns></returns>
public virtual T? Load<T>(String? path = null) where T : new()
{
EnsureLoad();
// 如果有命名空间则使用指定层级数据源
var source = path.IsNullOrEmpty() ? Root : GetSection(path);
if (source == null) return default;
var model = new T();
if (model is IConfigMapping map)
map.MapConfig(this, source);
else
source.MapTo(model, this);
return model;
}
/// <summary>保存配置树到数据源</summary>
public virtual Boolean SaveAll()
{
NotifyChange();
return true;
}
/// <summary>保存模型实例</summary>
/// <typeparam name="T">模型</typeparam>
/// <param name="model">模型实例</param>
/// <param name="path">路径。配置树位置</param>
public virtual Boolean Save<T>(T model, String? path = null)
{
if (model == null) throw new ArgumentNullException(nameof(model));
EnsureLoad();
// 如果有命名空间则使用指定层级数据源
var source = path.IsNullOrEmpty() ? Root : Find(path, true);
source?.MapFrom(model);
return SaveAll();
}
#endregion
#region
private readonly Dictionary<Object, String> _models = new Dictionary<Object, String>();
private readonly Dictionary<Object, ModelWrap> _models2 = new Dictionary<Object, ModelWrap>();
/// <summary>绑定模型,使能热更新,配置存储数据改变时同步修改模型属性</summary>
/// <typeparam name="T">模型。可通过实现IConfigMapping接口来自定义映射配置到模型实例</typeparam>
/// <param name="model">模型实例</param>
/// <param name="autoReload">是否自动更新。默认true</param>
/// <param name="path">命名空间。配置树位置,配置中心等多对象混合使用时</param>
public virtual void Bind<T>(T model, Boolean autoReload = true, String? path = null)
{
if (model == null) throw new ArgumentNullException(nameof(model));
EnsureLoad();
// 如果有命名空间则使用指定层级数据源
var source = path.IsNullOrEmpty() ? Root : GetSection(path);
if (source != null)
{
if (model is IConfigMapping map)
map.MapConfig(this, source);
else
source.MapTo(model, this);
}
if (autoReload && !_models.ContainsKey(model))
{
path ??= String.Empty;
_models.Add(model, path);
}
}
/// <summary>绑定模型,使能热更新,配置存储数据改变时同步修改模型属性</summary>
/// <typeparam name="T">模型。可通过实现IConfigMapping接口来自定义映射配置到模型实例</typeparam>
/// <param name="model">模型实例</param>
/// <param name="path">命名空间。配置树位置,配置中心等多对象混合使用时</param>
/// <param name="onChange">配置改变时执行的委托</param>
public virtual void Bind<T>(T model, String path, Action<IConfigSection> onChange)
{
if (model == null) throw new ArgumentNullException(nameof(model));
EnsureLoad();
// 如果有命名空间则使用指定层级数据源
var source = path.IsNullOrEmpty() ? Root : GetSection(path);
if (source != null)
{
if (model is IConfigMapping map)
map.MapConfig(this, source);
else
source.MapTo(model, this);
}
if (onChange != null && !_models2.ContainsKey(model))
{
_models2.Add(model, new ModelWrap(path, onChange));
}
}
private record ModelWrap(String Path, Action<IConfigSection> OnChange);
/// <summary>通知绑定对象,配置数据有改变</summary>
protected virtual void NotifyChange()
{
foreach (var item in _models)
{
var model = item.Key;
var source = GetSection(item.Value);
if (source != null)
{
if (model is IConfigMapping map)
map.MapConfig(this, source);
else
source.MapTo(model, this);
}
}
foreach (var item in _models2)
{
var model = item.Key;
var source = GetSection(item.Value.Path);
if (source != null) item.Value.OnChange(source);
}
// 通过事件通知外部
Changed?.Invoke(this, EventArgs.Empty);
}
#endregion
#region
/// <summary>默认提供者。默认xml</summary>
public static String DefaultProvider { get; set; } = "xml";
static ConfigProvider()
{
// 支持从命令行参数和环境变量设定默认配置提供者
var str = string.Empty;
var args = Environment.GetCommandLineArgs();
for (var i = 0; i < args.Length; i++)
{
if (args[i].EqualIgnoreCase("-DefaultConfig", "--DefaultConfig") && i + 1 < args.Length)
{
str = args[i + 1];
break;
}
}
if (str.IsNullOrEmpty()) str = NewLife.Runtime.GetEnvironmentVariable("DefaultConfig");
if (!str.IsNullOrEmpty()) DefaultProvider = str;
Register<InIConfigProvider>("ini");
Register<XmlConfigProvider>("xml");
Register<JsonConfigProvider>("json");
Register<XmlConfigProvider>("config");
}
private static readonly Dictionary<String, Type> _providers = new Dictionary<String, Type>(StringComparer.OrdinalIgnoreCase);
/// <summary>注册提供者</summary>
/// <typeparam name="TProvider"></typeparam>
/// <param name="name"></param>
public static void Register<TProvider>(String name) where TProvider : IConfigProvider, new() => _providers[name] = typeof(TProvider);
/// <summary>根据指定名称创建提供者</summary>
/// <remarks>
/// 如果是文件名,根据后缀确定使用哪一种提供者。
/// </remarks>
/// <param name="name"></param>
/// <returns></returns>
public static IConfigProvider? Create(String? name)
{
if (name.IsNullOrEmpty()) name = DefaultProvider;
var p = name.LastIndexOf('.');
var ext = p >= 0 ? name[(p + 1)..] : name;
if (!_providers.TryGetValue(ext, out _)) ext = DefaultProvider;
if (!_providers.TryGetValue(ext, out var type)) throw new Exception($"Unable to find an appropriate configuration provider for [{name}]");
var config = type.CreateInstance() as IConfigProvider;
return config;
}
#endregion
}