# 配置管理 (13_Config)

最后更新: 2024-09-20


# 📚 概述

DarkM 框架的配置管理模块提供了统一的配置存储、读取和变更通知机制。支持多种存储提供器(内存、缓存、Redis),并采用依赖注入方式管理所有配置类。

源代码位置: DarkM/src/Framework/Config


# 🏗️ 模块架构

# 项目结构

Config/
├── Config.Abstractions/           # 抽象层(接口、基类)
│   ├── IConfig.cs                 # 配置接口
│   ├── IConfigProvider.cs         # 配置提供器接口
│   ├── IConfigStorageProvider.cs  # 配置存储接口
│   ├── IConfigChangeEvent.cs      # 配置变更事件接口
│   ├── ConfigDescriptor.cs        # 配置描述符
│   ├── ConfigCollection.cs        # 配置集合
│   ├── ConfigConfig.cs            # 配置的配置
│   ├── ConfigEnumProvider.cs      # 配置提供器枚举
│   ├── ConfigType.cs              # 配置类型枚举
│   ├── ServiceCollectionExtensions.cs  # DI 扩展
│   └── Impl/                      # 内置配置实现
│       ├── SystemConfig.cs        # 系统配置
│       ├── ComponentConfig.cs     # 组件配置
│       └── PathConfig.cs          # 路径配置
│
├── Config.Core/                   # 核心实现
│   ├── ConfigProvider.cs          # 配置提供器
│   └── MemoryConfigStorageProvider.cs  # 内存存储提供器
│
├── Config.Cache/                  # 缓存实现
│   └── ConfigProvider.cs          # 基于缓存的配置提供器
│
└── Config.Redis/                  # Redis 实现
    └── ConfigProvider.cs          # 基于 Redis 的配置提供器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 🔧 核心概念

# 1. 配置接口 (IConfig)

所有配置类都必须实现 IConfig 接口:

namespace DarkM.Lib.Config.Abstractions
{
    /// <summary>
    /// 配置接口,所有配置类都要继承该接口
    /// </summary>
    public interface IConfig
    {
    }
}
1
2
3
4
5
6
7
8
9

示例配置类:

/// <summary>
/// 系统配置
/// </summary>
public class SystemConfig : IConfig
{
    /// <summary>
    /// 编号
    /// </summary>
    public string Code { get; set; }

    /// <summary>
    /// 系统标题
    /// </summary>
    public string Title { get; set; }

    /// <summary>
    /// 系统域名
    /// </summary>
    public string Domain { get; set; }

    /// <summary>
    /// Logo 文件路径
    /// </summary>
    public string Logo { get; set; }

    /// <summary>
    /// 版权声明
    /// </summary>
    public string Copyright { get; set; }

    /// <summary>
    /// 用户页 (前端页面路由名称)
    /// </summary>
    public string UserPage { get; set; }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# 2. 配置类型 (ConfigType)

public enum ConfigType
{
    /// <summary>
    /// 库配置(框架核心库)
    /// </summary>
    Library = 0,
    
    /// <summary>
    /// 模块配置(业务模块)
    /// </summary>
    Module = 1
}
1
2
3
4
5
6
7
8
9
10
11
12

# 3. 配置提供器 (ConfigEnumProvider)

[Description("配置配置器")]
public enum ConfigEnumProvider
{
    /// <summary>
    /// 默认配置器(内存存储)
    /// </summary>
    Core,
    
    /// <summary>
    /// 使用缓存作为配置存储(与 Cache 配置一致)
    /// </summary>
    Cache,
    
    /// <summary>
    /// 强制使用 Redis 作为配置存储
    /// </summary>
    Redis
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 4. 配置描述符 (ConfigDescriptor)

public class ConfigDescriptor
{
    /// <summary>
    /// 类型(Library/Module)
    /// </summary>
    public ConfigType Type { get; set; }

    /// <summary>
    /// 实现类型
    /// </summary>
    public Type ImplementType { get; set; }

    /// <summary>
    /// 编码(配置类名去掉 Config 后缀)
    /// </summary>
    public string Code { get; set; }

    /// <summary>
    /// 变更事件列表
    /// </summary>
    public List<Type> ChangeEvents { get; set; } = new List<Type>();

    /// <summary>
    /// 实例(运行时填充)
    /// </summary>
    [JsonIgnore]
    public IConfig Instance { get; set; }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 🎯 核心接口

# IConfigProvider(配置提供器)

public interface IConfigProvider
{
    /// <summary>
    /// 获取指定实现类的实例
    /// </summary>
    IConfig Get(Type implementType);

    /// <summary>
    /// 获取配置实例
    /// </summary>
    /// <param name="code">编码</param>
    /// <param name="type">类型</param>
    IConfig Get(string code, ConfigType type);

    /// <summary>
    /// 获取配置实例(泛型)
    /// </summary>
    TConfig Get<TConfig>() where TConfig : IConfig, new();

    /// <summary>
    /// 设置配置类
    /// </summary>
    /// <param name="type">类型</param>
    /// <param name="code">编码</param>
    /// <param name="json">JSON 数据</param>
    bool Set(ConfigType type, string code, string json);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# IConfigStorageProvider(配置存储提供器)

public interface IConfigStorageProvider
{
    /// <summary>
    /// 获取配置 Json 值
    /// </summary>
    Task<string> GetJson(ConfigType type, string code);

    /// <summary>
    /// 保存配置 Json 值
    /// </summary>
    Task<bool> SaveJson(ConfigType type, string code, string json);
}
1
2
3
4
5
6
7
8
9
10
11
12

# IConfigChangeEvent(配置变更事件)

public interface IConfigChangeEvent<TConfig> where TConfig : IConfig
{
    /// <summary>
    /// 配置变更时触发
    /// </summary>
    Task OnChange(TConfig oldConfig, TConfig newConfig);
}
1
2
3
4
5
6
7

# 🏗️ 依赖注入

# 自动注册机制

框架会自动扫描所有实现 IConfig 的类并注册到 DI 容器:

// ServiceCollectionExtensions.cs
public static class ServiceCollectionExtensions
{
    /// <summary>
    /// 添加配置项核心服务
    /// </summary>
    public static IServiceCollection AddConfigCore(this IServiceCollection services)
    {
        if (services.All(m => m.ServiceType != typeof(IConfigCollection)))
        {
            services.AddImplementTypes();
        }

        return services;
    }

    private static void AddImplementTypes(this IServiceCollection services)
    {
        var configs = new ConfigCollection();
        var assemblies = AssemblyHelper.Load();
        
        // 扫描所有程序集
        foreach (var assembly in assemblies)
        {
            // 查找所有实现 IConfig 的类
            var types = assembly.GetTypes()
                .Where(m => typeof(IConfig).IsImplementType(m) && typeof(IConfig) != m);
            
            foreach (var implType in types)
            {
                if (implType.FullName.NotNull())
                {
                    var descriptor = new ConfigDescriptor
                    {
                        Type = implType.FullName.Contains(".Lib.") 
                            ? ConfigType.Library 
                            : ConfigType.Module,
                        Code = implType.Name.Replace("Config", ""),
                        ImplementType = implType
                    };

                    configs.Add(descriptor);
                }
            }
        }

        // 注入变更事件
        services.AddChangedEvent(assemblies, configs);

        // 注册配置集合
        services.AddSingleton<IConfigCollection>(configs);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

# 💡 使用示例

# 1. 定义配置类

using DarkM.Lib.Config.Abstractions;

namespace YourModule.Config
{
    /// <summary>
    /// 邮件配置
    /// </summary>
    public class EmailConfig : IConfig
    {
        /// <summary>
        /// SMTP 服务器
        /// </summary>
        public string SmtpServer { get; set; }

        /// <summary>
        /// SMTP 端口
        /// </summary>
        public int SmtpPort { get; set; } = 25;

        /// <summary>
        /// 发件人邮箱
        /// </summary>
        public string FromEmail { get; set; }

        /// <summary>
        /// 发件人密码
        /// </summary>
        public string Password { get; set; }

        /// <summary>
        /// 是否启用 SSL
        /// </summary>
        public bool EnableSsl { get; set; } = false;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# 2. 监听配置变更

using DarkM.Lib.Config.Abstractions;

public class EmailConfigChangeEvent : IConfigChangeEvent<EmailConfig>
{
    private readonly ILogger<EmailConfigChangeEvent> _logger;

    public EmailConfigChangeEvent(ILogger<EmailConfigChangeEvent> logger)
    {
        _logger = logger;
    }

    public Task OnChange(EmailConfig oldConfig, EmailConfig newConfig)
    {
        _logger.LogInformation(
            "邮件配置已更新:{OldSmtp} -> {NewSmtp}",
            oldConfig?.SmtpServer,
            newConfig.SmtpServer);

        // 重新初始化邮件客户端
        // ReinitializeSmtpClient(newConfig);

        return Task.CompletedTask;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 3. 读取配置

public class EmailService
{
    private readonly IConfigProvider _configProvider;
    private EmailConfig _emailConfig;

    public EmailService(IConfigProvider configProvider)
    {
        _configProvider = configProvider;
        _emailConfig = _configProvider.Get<EmailConfig>();
    }

    public async Task SendEmail(string to, string subject, string body)
    {
        // 使用配置
        using var client = new SmtpClient(_emailConfig.SmtpServer, _emailConfig.SmtpPort)
        {
            Credentials = new NetworkCredential(_emailConfig.FromEmail, _emailConfig.Password),
            EnableSsl = _emailConfig.EnableSsl
        };

        // 发送邮件...
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 4. 更新配置

public class ConfigController : ModuleController
{
    private readonly IConfigProvider _configProvider;

    public ConfigController(IConfigProvider configProvider)
    {
        _configProvider = configProvider;
    }

    [HttpPost("update-email")]
    public async Task<IResultModel> UpdateEmailConfig([FromBody] EmailConfig config)
    {
        var json = JsonConvert.SerializeObject(config);
        var success = _configProvider.Set(
            ConfigType.Module, 
            "Email", 
            json);

        return success 
            ? ResultModel.Success("配置已更新") 
            : ResultModel.Error("配置更新失败");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 🔧 配置存储提供器

# MemoryConfigStorageProvider(内存存储)

// Config.Core/MemoryConfigStorageProvider.cs
public class MemoryConfigStorageProvider : IConfigStorageProvider
{
    private readonly ConcurrentDictionary<string, string> _storage 
        = new ConcurrentDictionary<string, string>();

    public Task<string> GetJson(ConfigType type, string code)
    {
        var key = $"{type}:{code}";
        _storage.TryGetValue(key, out var json);
        return Task.FromResult(json);
    }

    public Task<bool> SaveJson(ConfigType type, string code, string json)
    {
        var key = $"{type}:{code}";
        _storage.AddOrUpdate(key, json, (_, _) => json);
        return Task.FromResult(true);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# Config.Cache(缓存存储)

使用框架的缓存模块作为配置存储,支持分布式缓存。

# Config.Redis(Redis 存储)

强制使用 Redis 作为配置存储,适用于需要持久化的场景。


# 📋 内置配置类

# SystemConfig(系统配置)

public class SystemConfig : IConfig
{
    public string Code { get; set; }        // 编号
    public string Title { get; set; }       // 系统标题
    public string Domain { get; set; }      // 系统域名
    public string Logo { get; set; }        // Logo 文件路径
    public string Copyright { get; set; }   // 版权声明
    public string UserPage { get; set; }    // 用户页路由名称
}
1
2
3
4
5
6
7
8
9

# ComponentConfig(组件配置)

public class ComponentConfig : IConfig
{
    public string Code { get; set; }
    public string Name { get; set; }
    public string Version { get; set; }
    public bool Enabled { get; set; }
    public string ConfigJson { get; set; }
}
1
2
3
4
5
6
7
8

# PathConfig(路径配置)

public class PathConfig : IConfig
{
    public string Code { get; set; }
    public string RootPath { get; set; }
    public string UploadPath { get; set; }
    public string TempPath { get; set; }
    public string LogPath { get; set; }
}
1
2
3
4
5
6
7
8

# 💡 最佳实践

# 1. 配置类命名规范

// ✅ 推荐:*Config 后缀
public class EmailConfig : IConfig { }
public class SmsConfig : IConfig { }
public class PaymentConfig : IConfig { }

// ❌ 避免:其他命名
public class EmailSettings { }
public class SmsOptions { }
1
2
3
4
5
6
7
8

# 2. 配置变更处理

// ✅ 推荐:实现变更事件
public class EmailConfigChangeEvent : IConfigChangeEvent<EmailConfig>
{
    public Task OnChange(EmailConfig oldConfig, EmailConfig newConfig)
    {
        // 1. 记录日志
        // 2. 重新初始化相关服务
        // 3. 通知其他组件
        return Task.CompletedTask;
    }
}

// ❌ 避免:轮询检查配置变化
1
2
3
4
5
6
7
8
9
10
11
12
13

# 3. 配置验证

public class EmailConfigChangeEvent : IConfigChangeEvent<EmailConfig>
{
    public Task OnChange(EmailConfig oldConfig, EmailConfig newConfig)
    {
        // 验证新配置
        if (newConfig.SmtpPort < 1 || newConfig.SmtpPort > 65535)
        {
            throw new ArgumentException("SMTP 端口必须在 1-65535 之间");
        }

        if (string.IsNullOrEmpty(newConfig.FromEmail))
        {
            throw new ArgumentException("发件人邮箱不能为空");
        }

        // 验证通过后应用配置
        ApplyConfig(newConfig);
        return Task.CompletedTask;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 4. 配置存储选择

场景 推荐存储 说明
开发环境 Core(内存) 快速、无需外部依赖
单机部署 Cache(内存缓存) 与框架缓存一致
集群部署 Redis 分布式配置共享
需要持久化 Redis 配置数据持久化

# 🔍 常见问题

# Q1: 配置类未被识别?

检查清单:

  1. ✅ 配置类是否实现了 IConfig 接口?
  2. ✅ 配置类所在的程序集是否被加载?
  3. ✅ 配置类是否有无参构造函数?
  4. ✅ 配置类属性是否有 getter/setter?

# Q2: 配置变更事件未触发?

检查清单:

  1. ✅ 变更事件类是否实现了 IConfigChangeEvent<TConfig>
  2. ✅ 变更事件类是否注册到 DI 容器?
  3. ✅ 配置更新是否使用 IConfigProvider.Set() 方法?

# Q3: 如何选择合适的存储提供器?

// appsettings.json
{
  "Config": {
    "Provider": "Redis"  // Core/Cache/Redis
  }
}
1
2
3
4
5
6
Provider 适用场景 优点 缺点
Core 开发环境、单机测试 快速、无依赖 重启丢失
Cache 单机部署 与缓存一致、性能好 重启丢失
Redis 集群部署、生产环境 持久化、分布式共享 需要 Redis 服务

# 📚 相关文档


# 🔗 参考链接


最后更新: 2024-09-20