2002年以前,配置主流是Ini文件,简单易用;2002年以后,配置主流是Xml文件,结构化数据更严格,SOA更是把它带向更高的层次;2012年以后,Json随着Web流程而兴起,Json配置文件成为主流;在这个过程中,中大型系统逐步向分布式系统过渡,配置中心逐步成为主流。
总结近20年的经验,抽象建立了一套配置系统IConfigProvider。
Nuget包:NewLife.Core
源码:https://github.com/NewLifeX/X/tree/master/NewLife.Core/Configuration
视频:https://www.bilibili.com/video/BV1We4y1q7Wi
最佳实践
实践表明,Xml配置文件相对简单易用,只需要写个配置类,如果配置文件不存在,系统能够自动创建一个。例如,核心库配置类 NewLife.Setting.Current.LogLevel 即可访问配置项,自动创建 Config/Core.config 配置文件。
/// <summary>核心设置</summary> [DisplayName("核心设置")] [Config("Core")] public class Setting : Config<Setting> { #region 属性 /// <summary>是否启用全局调试。默认启用</summary> [Description("全局调试。XTrace.Debug")] public Boolean Debug { get; set; } = true; /// <summary>日志等级,只输出大于等于该级别的日志,All/Debug/Info/Warn/Error/Fatal,默认Info</summary> [Description("日志等级。只输出大于等于该级别的日志,All/Debug/Info/Warn/Error/Fatal,默认Info")] public LogLevel LogLevel { get; set; } = LogLevel.Info; /// <summary>文件日志目录。默认Log子目录</summary> [Description("文件日志目录。默认Log子目录")] public String LogPath { get; set; } = ""; /// <summary>日志文件上限。超过上限后拆分新日志文件,默认10MB,0表示不限制大小</summary> [Description("日志文件上限。超过上限后拆分新日志文件,默认10MB,0表示不限制大小")] public Int32 LogFileMaxBytes { get; set; } = 10; /// <summary>日志文件备份。超过备份数后,最旧的文件将被删除,默认100,0表示不限制个数</summary> [Description("日志文件备份。超过备份数后,最旧的文件将被删除,默认100,0表示不限制个数")] public Int32 LogFileBackups { get; set; } = 100; /// <summary>日志文件格式。默认{0:yyyy_MM_dd}.log,支持日志等级如 {1}_{0:yyyy_MM_dd}.log</summary> [Description("日志文件格式。默认{0:yyyy_MM_dd}.log,支持日志等级如 {1}_{0:yyyy_MM_dd}.log")] public String LogFileFormat { get; set; } = "{0:yyyy_MM_dd}.log"; /// <summary>网络日志。本地子网日志广播udp://255.255.255.255:514,或者http://xxx:80/log</summary> [Description("网络日志。本地子网日志广播udp://255.255.255.255:514,或者http://xxx:80/log")] public String NetworkLog { get; set; } = ""; /// <summary>数据目录。本地数据库目录,默认Data子目录</summary> [Description("数据目录。本地数据库目录,默认Data子目录")] public String DataPath { get; set; } = ""; /// <summary>备份目录。备份数据库时存放的目录,默认Backup子目录</summary> [Description("备份目录。备份数据库时存放的目录,默认Backup子目录")] public String BackupPath { get; set; } = ""; /// <summary>插件目录</summary> [Description("插件目录")] public String PluginPath { get; set; } = "Plugins"; /// <summary>插件服务器。将从该网页上根据关键字分析链接并下载插件</summary> [Description("插件服务器。将从该网页上根据关键字分析链接并下载插件")] public String PluginServer { get; set; } = "http://x.newlifex.com/"; #endregion #region 方法 /// <summary>加载完成后</summary> protected override void OnLoaded() { if (LogPath.IsNullOrEmpty()) LogPath = "Log"; if (DataPath.IsNullOrEmpty()) DataPath = "Data"; if (BackupPath.IsNullOrEmpty()) BackupPath = "Backup"; if (LogFileFormat.IsNullOrEmpty()) LogFileFormat = "{0:yyyy_MM_dd}.log"; if (PluginServer.IsNullOrWhiteSpace()) PluginServer = "http://x.newlifex.com/"; base.OnLoaded(); } /// <summary>获取插件目录</summary> /// <returns></returns> public String GetPluginPath() => PluginPath.GetBasePath(); #endregion }
配置核心IConfigProvider
整个配置系统的核心是IConfigProvider接口,内置有 JsonConfigProvider/XmlConfigProvider/IniConfigProvider/HttpConfigProvider等多个配置实现。
全貌如下:
/// <summary>配置提供者</summary> /// <remarks> /// 建立树状配置数据体系,以分布式配置中心为核心,支持基于key的索引读写,也支持Load/Save/Bind的实体模型转换。 /// key索引支持冒号分隔的多层结构,在配置中心中不同命名空间使用不同提供者实例,在文件配置中不同文件使用不同提供者实例。 /// /// 一个配置类,支持从不同持久化提供者读取,可根据需要选择配置持久化策略。 /// 例如,小系统采用ini/xml/json文件配置,分布式系统采用配置中心。 /// /// 可通过实现IConfigMapping接口来自定义映射配置到模型实例。 /// </remarks> public interface IConfigProvider { /// <summary>名称</summary> String Name { get; set; } /// <summary>所有键</summary> ICollection<String> Keys { get; } /// <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> GetConfigCallback GetConfig { get; } /// <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); }
IConfigProvider最常见用法是根据索引器访问数据,例如provider["Title"]。
高级用法是Load和Bind,两者都是把配置数据填充到目标对象的属性上。Bind绑定多了自动刷新,在配置数据改变时,能够及时更新数据到配置对象的属性上。
Json配置
以Json格式存储配置数据,不支持Json注释,支持无限层嵌套。
配置内容格式如下:
{ "#Debug": "全局调试。XTrace.Debug", "Debug": "true", "#LogLevel": "日志等级。只输出大于等于该级别的日志,All/Debug/Info/Warn/Error/Fatal,默认Info", "LogLevel": "Fatal", "#LogPath": "文件日志目录", "LogPath": "xxx", "#NetworkLog": "网络日志。本地子网日志广播255.255.255.255:514", "NetworkLog": "255.255.255.255:514", "#LogFileFormat": "日志文件格式。默认{0:yyyy_MM_dd}.log", "LogFileFormat": "{0:yyyy_MM_dd}.log", "#TempPath": "临时目录", "TempPath": "yyy", "#PluginPath": "插件目录", "PluginPath": "Plugins", "#PluginServer": "插件服务器。将从该网页上根据关键字分析链接并下载插件", "PluginServer": "http://x.newlifex.com/", "#Sys": "系统配置", "Sys": { "#Name": "用于标识系统的英文名", "Name": "NewLife.Cube", "#Version": "系统版本", "Version": "", "#DisplayName": "用户可见的名称", "DisplayName": "魔方平台", "#Company": "公司", "Company": "新生命开发团队", "#Instance": "应用实例。单应用多实例部署时用于唯一标识实例节点", "Instance": "0", "#Develop": "开发者模式", "Develop": "true", "#Enable": "启用", "Enable": "true", "#InstallTime": "安装时间", "InstallTime": "2021-04-18 18:06:12" } }
常见用法如下:
var _provider = new JsonConfigProvider { FileName = "Config/core1.json" }; var set = new ConfigModel { Debug = true, LogLevel = LogLevel.Fatal, LogPath = "xxx", NetworkLog = "255.255.255.255:514", TempPath = "yyy", Sys = new SysConfig { Name = "NewLife.Cube", DisplayName = "魔方平台", Company = "新生命开发团队", }, }; _provider.Save(set); var prv = _provider; Assert.NotNull(prv); Assert.Equal(set.Debug.ToString().ToLower(), prv["Debug"]); Assert.Equal(set.LogLevel + "", prv["LogLevel"]); Assert.Equal(set.LogPath, prv["LogPath"]); Assert.Equal(set.NetworkLog, prv["NetworkLog"]); Assert.Equal(set.LogFileFormat, prv["LogFileFormat"]); Assert.Equal(set.TempPath, prv["TempPath"]); Assert.Equal(set.PluginPath, prv["PluginPath"]); Assert.Equal(set.PluginServer, prv["PluginServer"]); Assert.Equal("全局调试。XTrace.Debug", prv.GetSection("Debug").Comment); Assert.Equal("系统配置", prv.GetSection("Sys").Comment); Assert.Equal("用于标识系统的英文名", prv.GetSection("Sys:Name").Comment); var sys = set.Sys; Assert.Equal(sys.Name, prv["Sys:Name"]); Assert.Equal(sys.DisplayName, prv["Sys:DisplayName"]); Assert.Equal(sys.Company, prv["Sys:Company"]); var prv2 = new JsonConfigProvider { FileName = (_provider as FileConfigProvider).FileName }; var set2 = prv2.Load<ConfigModel>(); Assert.NotNull(set2); Assert.Equal(set.Debug, set2.Debug); Assert.Equal(set.LogLevel, set2.LogLevel); Assert.Equal(set.LogPath, set2.LogPath); Assert.Equal(set.NetworkLog, set2.NetworkLog); Assert.Equal(set.LogFileFormat, set2.LogFileFormat); Assert.Equal(set.TempPath, set2.TempPath); Assert.Equal(set.PluginPath, set2.PluginPath); Assert.Equal(set.PluginServer, set2.PluginServer); Assert.Equal("全局调试。XTrace.Debug", prv2.GetSection("Debug").Comment); Assert.Equal("系统配置", prv2.GetSection("Sys").Comment); Assert.Equal("用于标识系统的英文名", prv2.GetSection("Sys:Name").Comment); var sys2 = set2.Sys; Assert.NotNull(sys2); Assert.Equal(sys.Name, sys2.Name); Assert.Equal(sys.DisplayName, sys2.DisplayName); Assert.Equal(sys.Company, sys2.Company);
Xml配置
以Xml格式存储配置数据,支持注释,支持无限层嵌套。
配置内容格式如下:
<?xml version="1.0" encoding="utf-8"?> <Root> <!--全局调试。XTrace.Debug--> <Debug>true</Debug> <!--日志等级。只输出大于等于该级别的日志,All/Debug/Info/Warn/Error/Fatal,默认Info--> <LogLevel>Fatal</LogLevel> <!--文件日志目录--> <LogPath>xxx</LogPath> <!--网络日志。本地子网日志广播255.255.255.255:514--> <NetworkLog>255.255.255.255:514</NetworkLog> <!--日志文件格式。默认{0:yyyy_MM_dd}.log--> <LogFileFormat>{0:yyyy_MM_dd}.log</LogFileFormat> <!--临时目录--> <TempPath>yyy</TempPath> <!--插件目录--> <PluginPath>Plugins</PluginPath> <!--插件服务器。将从该网页上根据关键字分析链接并下载插件--> <PluginServer>http://x.newlifex.com/</PluginServer> <!--系统配置--> <Sys> <!--用于标识系统的英文名--> <Name>NewLife.Cube</Name> <!--系统版本--> <Version></Version> <!--用户可见的名称--> <DisplayName>魔方平台</DisplayName> <!--公司--> <Company>新生命开发团队</Company> <!--应用实例。单应用多实例部署时用于唯一标识实例节点--> <Instance>0</Instance> <!--开发者模式--> <Develop>true</Develop> <!--启用--> <Enable>true</Enable> <!--安装时间--> <InstallTime>2021-04-18 18:06:12</InstallTime> </Sys> </Root>
常见用法如下:
var _provider = new XmlConfigProvider { FileName = "Config/core1.xml" }; var set = new ConfigModel { Debug = true, LogLevel = LogLevel.Fatal, LogPath = "xxx", NetworkLog = "255.255.255.255:514", TempPath = "yyy", Sys = new SysConfig { Name = "NewLife.Cube", DisplayName = "魔方平台", Company = "新生命开发团队", }, }; _provider.Save(set); var prv = _provider; Assert.NotNull(prv); Assert.Equal(set.Debug.ToString().ToLower(), prv["Debug"]); Assert.Equal(set.LogLevel + "", prv["LogLevel"]); Assert.Equal(set.LogPath, prv["LogPath"]); Assert.Equal(set.NetworkLog, prv["NetworkLog"]); Assert.Equal(set.LogFileFormat, prv["LogFileFormat"]); Assert.Equal(set.TempPath, prv["TempPath"]); Assert.Equal(set.PluginPath, prv["PluginPath"]); Assert.Equal(set.PluginServer, prv["PluginServer"]); Assert.Equal("全局调试。XTrace.Debug", prv.GetSection("Debug").Comment); Assert.Equal("系统配置", prv.GetSection("Sys").Comment); Assert.Equal("用于标识系统的英文名", prv.GetSection("Sys:Name").Comment); var sys = set.Sys; Assert.Equal(sys.Name, prv["Sys:Name"]); Assert.Equal(sys.DisplayName, prv["Sys:DisplayName"]); Assert.Equal(sys.Company, prv["Sys:Company"]); var prv2 = new XmlConfigProvider { FileName = (_provider as FileConfigProvider).FileName }; var set2 = prv2.Load<ConfigModel>(); Assert.NotNull(set2); Assert.Equal(set.Debug, set2.Debug); Assert.Equal(set.LogLevel, set2.LogLevel); Assert.Equal(set.LogPath, set2.LogPath); Assert.Equal(set.NetworkLog, set2.NetworkLog); Assert.Equal(set.LogFileFormat, set2.LogFileFormat); Assert.Equal(set.TempPath, set2.TempPath); Assert.Equal(set.PluginPath, set2.PluginPath); Assert.Equal(set.PluginServer, set2.PluginServer); Assert.Equal("全局调试。XTrace.Debug", prv2.GetSection("Debug").Comment); Assert.Equal("系统配置", prv2.GetSection("Sys").Comment); Assert.Equal("用于标识系统的英文名", prv2.GetSection("Sys:Name").Comment); var sys2 = set2.Sys; Assert.NotNull(sys2); Assert.Equal(sys.Name, sys2.Name); Assert.Equal(sys.DisplayName, sys2.DisplayName); Assert.Equal(sys.Company, sys2.Company);
Ini配置
以Ini格式存储配置数据,支持注释,支持两层结构。
配置内容格式如下:
; 全局调试。XTrace.Debug Debug = true ; 日志等级。只输出大于等于该级别的日志,All/Debug/Info/Warn/Error/Fatal,默认Info LogLevel = Fatal ; 文件日志目录 LogPath = xxx ; 网络日志。本地子网日志广播255.255.255.255:514 NetworkLog = 255.255.255.255:514 ; 日志文件格式。默认{0:yyyy_MM_dd}.log LogFileFormat = {0:yyyy_MM_dd}.log ; 临时目录 TempPath = yyy ; 插件目录 PluginPath = Plugins ; 插件服务器。将从该网页上根据关键字分析链接并下载插件 PluginServer = http://x.newlifex.com/ ; 系统配置 [Sys] ; 用于标识系统的英文名 Name = NewLife.Cube ; 系统版本 Version = ; 用户可见的名称 DisplayName = 魔方平台 ; 公司 Company = 新生命开发团队 ; 应用实例。单应用多实例部署时用于唯一标识实例节点 Instance = 0 ; 开发者模式 Develop = true ; 启用 Enable = true ; 安装时间 InstallTime = 2021-04-18 18:06:12
常见用法如下:
var _provider = new InIConfigProvider { FileName = "Config/core1.ini" }; var set = new ConfigModel { Debug = true, LogLevel = LogLevel.Fatal, LogPath = "xxx", NetworkLog = "255.255.255.255:514", TempPath = "yyy", Sys = new SysConfig { Name = "NewLife.Cube", DisplayName = "魔方平台", Company = "新生命开发团队", }, }; _provider.Save(set); var prv = _provider; Assert.NotNull(prv); Assert.Equal(set.Debug.ToString().ToLower(), prv["Debug"]); Assert.Equal(set.LogLevel + "", prv["LogLevel"]); Assert.Equal(set.LogPath, prv["LogPath"]); Assert.Equal(set.NetworkLog, prv["NetworkLog"]); Assert.Equal(set.LogFileFormat, prv["LogFileFormat"]); Assert.Equal(set.TempPath, prv["TempPath"]); Assert.Equal(set.PluginPath, prv["PluginPath"]); Assert.Equal(set.PluginServer, prv["PluginServer"]); Assert.Equal("全局调试。XTrace.Debug", prv.GetSection("Debug").Comment); Assert.Equal("系统配置", prv.GetSection("Sys").Comment); Assert.Equal("用于标识系统的英文名", prv.GetSection("Sys:Name").Comment); var sys = set.Sys; Assert.Equal(sys.Name, prv["Sys:Name"]); Assert.Equal(sys.DisplayName, prv["Sys:DisplayName"]); Assert.Equal(sys.Company, prv["Sys:Company"]); var prv2 = new InIConfigProvider { FileName = (_provider as FileConfigProvider).FileName }; var set2 = prv2.Load<ConfigModel>(); Assert.NotNull(set2); Assert.Equal(set.Debug, set2.Debug); Assert.Equal(set.LogLevel, set2.LogLevel); Assert.Equal(set.LogPath, set2.LogPath); Assert.Equal(set.NetworkLog, set2.NetworkLog); Assert.Equal(set.LogFileFormat, set2.LogFileFormat); Assert.Equal(set.TempPath, set2.TempPath); Assert.Equal(set.PluginPath, set2.PluginPath); Assert.Equal(set.PluginServer, set2.PluginServer); Assert.Equal("全局调试。XTrace.Debug", prv2.GetSection("Debug").Comment); Assert.Equal("系统配置", prv2.GetSection("Sys").Comment); Assert.Equal("用于标识系统的英文名", prv2.GetSection("Sys:Name").Comment); var sys2 = set2.Sys; Assert.NotNull(sys2); Assert.Equal(sys.Name, sys2.Name); Assert.Equal(sys.DisplayName, sys2.DisplayName); Assert.Equal(sys.Company, sys2.Company);
Http配置(配置中心)
通过调用Http接口拉取配置信息。
集成星尘配置中心:
[Fact] public void TestStardust() { var provider = new HttpConfigProvider { Server = "http://star.newlifex.com:6600", //Server = "http://localhost:6600", AppId = "StarWeb" }; var str = provider["test1"]; Assert.NotEmpty(str); var keys = provider.Keys.ToArray(); Assert.NotNull(keys); var model = provider.Load<Model2>(); Assert.NotNull(model); Assert.NotEmpty(model.Test); Assert.Equal(str, model.Test); Assert.NotEmpty(model.Shop); Assert.NotEmpty(model.Title); Assert.Equal("NewLife开发团队", model.Title); var model2 = new Model2(); provider.Bind(model2); Assert.Equal(str, model2.Test); Assert.NotEmpty(model.Shop); Assert.Equal("NewLife开发团队", model.Title); provider.LoadAll(); } private class Model2 { [DataMember(Name = "test1")] public String Test { get; set; } [DataMember(Name = "conn_Shop")] public String Shop { get; set; } public String Title { get; set; } }
Apollo阿波罗配置
集成阿波罗配置中心
public void TestApollo() { var provider = new ApolloConfigProvider { Server = _server, AppId = "testapi" }; provider.SetApollo("application"); //provider.LoadAll(); var url = provider["appapiurl"]; Assert.NotEmpty(url); var keys = provider.Keys.ToArray(); Assert.NotNull(keys); var model = provider.Load<Model>(); Assert.NotNull(model); Assert.NotEmpty(model.AppApiUrl); Assert.Equal(url, model.AppApiUrl); Assert.True(model.Radius > 0); Assert.NotEmpty(model.MySqlServer); var model2 = new Model(); provider.Bind(model2); Assert.Equal(url, model2.AppApiUrl); Assert.True(model2.Radius > 0); Assert.NotEmpty(model2.MySqlServer); } private class Model { public Int32 Radius { get; set; } public String MySqlServer { get; set; } public String AppApiUrl { get; set; } }