Loading

造轮子之设置管理

前面我们基础设施基本搭建完毕,后面可以做一些稍微复杂点的功能了,接下来就来实现一个设置管理。
设置管理一般用做一些系统设置之类的,如邮箱配置等,面向使用人员。而不需要修改我们的配置文件,修改配置文件的方式就偏向于技术人员了。
话不多说,开造。

设计结构

设置管理中需要2个表,一个是设置组表,比如什么邮箱设置是一个分组,公众号设置是一个分组。一个是设置的值的存储表,用作存储分组的设置。

using Wheel.Domain.Common;

namespace Wheel.Domain.Settings
{
    public class SettingGroup : Entity
    {
        public string Name { get; set; }
        public string NormalizedName { get; set; }

        public virtual ICollection<SettingValue> SettingValues { get; set; }
    }
}
using Wheel.Domain.Common;
using Wheel.Enums;

namespace Wheel.Domain.Settings
{
    public class SettingValue : Entity
    {
        public virtual long SettingGroupId { get; set; }
        public virtual SettingGroup SettingGroup { get; set; }
        public string Key { get; set; }
        public string Value { get; set; }
        public SettingValueType ValueType { get; set; }
        public SettingScope SettingScope { get; set; }
        public string? SettingScopeKey { get; set; }
    }
}

这里有两个枚举值,分别是SettingValueType和SettingScope
SettingValueType是Value的类型,如字符串,布尔值,整型,浮点数,主要用于配合前端做页面展示格式以及修改配置时的数据校验。
SettingScope表示设置的生效范围,比如全局设置,用户设置等等,SettingScopeKey则用作存储范围关联的键值,比如用户范围的话,SettingScopeKey就约定存UserId作为键值,当然也可以自己约定别的唯一数用作关联。后续都可以扩展。

namespace Wheel.Enums
{
    public enum SettingValueType
    {
        /// <summary>
        /// 布尔值
        /// </summary>
        Bool,
        /// <summary>
        /// 整型
        /// </summary>
        Int,
        /// <summary>
        /// 长整型
        /// </summary>
        Long,
        /// <summary>
        /// 64位双精度浮点型
        /// </summary>
        Double,
        /// <summary>
        /// 128位精确的十进制值
        /// </summary>
        Decimal,
        /// <summary>
        /// 字符串
        /// </summary>
        String,
        /// <summary>
        /// Json对象
        /// </summary>
        JsonObject
    }
}
namespace Wheel.Enums
{
    public enum SettingScope
    {
        /// <summary>
        /// 全局设置
        /// </summary>
        Global,
        /// <summary>
        /// 用户设置
        /// </summary>
        User,
    }
}

修改DbContext

在DbContext中添加代码

#region Setting
public DbSet<SettingGroup> SettingGroups { get; set; }
public DbSet<SettingValue> SettingValues { get; set; }
#endregion

protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);

    ConfigureIdentity(builder);
    ConfigureLocalization(builder);
    ConfigurePermissionGrants(builder);
    ConfigureMenus(builder);
    ConfigureSettings(builder);
}

void ConfigureSettings(ModelBuilder builder)
{
    builder.Entity<SettingGroup>(b =>
    {
        b.HasKey(o => o.Id);
        b.Property(o => o.Name).HasMaxLength(128);
        b.Property(o => o.NormalizedName).HasMaxLength(128);
        b.HasMany(o => o.SettingValues).WithOne(o => o.SettingGroup);
        b.HasIndex(o => o.Name);
    });
    builder.Entity<SettingValue>(b =>
    {
        b.HasKey(o => o.Id);
        b.Property(o => o.Key).HasMaxLength(128);
        b.Property(o => o.SettingScopeKey).HasMaxLength(128);
        b.Property(o => o.ValueType).HasMaxLength(2048);
        b.HasOne(o => o.SettingGroup).WithMany(o => o.SettingValues);
        b.HasIndex(o => o.Key);
    });
}

然后执行数据库迁移命令修改数据库即可。

SettingManager

接下来实现一个SettingManager用于管理设置。

using HotChocolate.Types.Relay;
using System;
using System.Linq;
using Wheel.DependencyInjection;
using Wheel.Enums;
using Wheel.EventBus.Distributed;
using Wheel.EventBus.EventDatas;
using Wheel.Uow;
using Wheel.Utilities;

namespace Wheel.Domain.Settings
{
    public class SettingManager : ITransientDependency
    {
        private readonly IBasicRepository<SettingGroup, long> _settingGroupRepository;
        private readonly IBasicRepository<SettingValue, long> _settingValueRepository;

        private readonly IUnitOfWork _unitOfWork;
        private readonly SnowflakeIdGenerator _snowflakeIdGenerator;
        private readonly IDistributedEventBus _distributedEventBus;

        public SettingManager(IBasicRepository<SettingGroup, long> settingGroupRepository, IBasicRepository<SettingValue, long> settingValueRepository, IUnitOfWork unitOfWork, SnowflakeIdGenerator snowflakeIdGenerator, IDistributedEventBus distributedEventBus)
        {
            _settingGroupRepository = settingGroupRepository;
            _settingValueRepository = settingValueRepository;
            _unitOfWork = unitOfWork;
            _snowflakeIdGenerator = snowflakeIdGenerator;
            _distributedEventBus = distributedEventBus;
        }

        public async Task<T?> GetSettingValue<T>(string settingGroupName, string settingKey, SettingScope settingScope = SettingScope.Golbal, string? settingScopeKey = null, CancellationToken cancellationToken = default)
        {
            var settingGroup = await _settingGroupRepository.FindAsync(a => a.Name == settingGroupName, cancellationToken);

            if (settingGroup is null)
            {
                throw new ArgumentException($"SettingGroup: {settingGroup} Not Exist.");
            }

            var settingValue = settingGroup?.SettingValues.FirstOrDefault(a => a.Key == settingKey && a.SettingScope == settingScope && a.SettingScopeKey == settingScopeKey);

            if (settingValue is null)
                return default;

            if(settingValue.ValueType == SettingValueType.JsonObject)
                return settingValue.Value.ToObject<T>();

            return (T)Convert.ChangeType(settingValue, typeof(T));
        }

        public async Task<SettingValue?> GetSettingValue(string settingGroupName, string settingKey, SettingScope settingScope = SettingScope.Golbal, string? settingScopeKey = null, CancellationToken cancellationToken = default)
        {
            var settingGroup = await _settingGroupRepository.FindAsync(a => a.Name == settingGroupName, cancellationToken);

            if (settingGroup is null)
            {
                throw new ArgumentException($"SettingGroup: {settingGroup} Not Exist.");
            }

            var settingValue = settingGroup?.SettingValues.FirstOrDefault(a => a.Key == settingKey && a.SettingScope == settingScope && a.SettingScopeKey == settingScopeKey);
            
            return settingValue;
        }

        public async Task<List<SettingValue>?> GetSettingValues(string settingGroupName, SettingScope settingScope = SettingScope.Golbal, string? settingScopeKey = null, CancellationToken cancellationToken = default)
        {
            var settingGroup = await _settingGroupRepository.FindAsync(a => a.Name == settingGroupName, cancellationToken);

            if (settingGroup is null)
            {
                throw new ArgumentException($"SettingGroup: {settingGroup} Not Exist.");
            }

            var settingValues = settingGroup?.SettingValues.Where(a => a.SettingScope == settingScope && a.SettingScopeKey == settingScopeKey).ToList();

            return settingValues;
        }

        public async Task SetSettingValue(string settingGroupName, SettingValue settingValue, CancellationToken cancellationToken = default)
        {
            using (var uow = await _unitOfWork.BeginTransactionAsync(cancellationToken))
            {
                try
                {
                    var settingGroup = await _settingGroupRepository.FindAsync(a => a.Name == settingGroupName, cancellationToken);
                    if (settingGroup is null)
                        settingGroup = await _settingGroupRepository.InsertAsync(new SettingGroup { Id = _snowflakeIdGenerator.Create(), Name = settingGroupName, NormalizedName = settingGroupName.ToUpper() }, cancellationToken: cancellationToken);
                    

                    CheckSettingValueType(settingValue.Value, settingValue.ValueType);

                    var sv = await _settingValueRepository.FindAsync(a=> a.SettingGroupId == settingGroup.Id && a.Id == settingValue.Id, cancellationToken);
                    if(sv is null)
                    {
                        settingValue.Id = _snowflakeIdGenerator.Create();
                        settingValue.SettingGroupId = settingGroup.Id;
                        await _settingValueRepository.InsertAsync(settingValue, cancellationToken: cancellationToken);
                    }
                    else
                        await _settingValueRepository.UpdateAsync(settingValue, cancellationToken: cancellationToken);
                    
                    await uow.CommitAsync(cancellationToken);
                    await _distributedEventBus.PublishAsync(new UpdateSettingEventData() { GroupName = settingGroupName, SettingScope = settingValue.SettingScope, SettingScopeKey = settingValue.SettingScopeKey });
                }
                catch(Exception ex)
                {
                    await uow.RollbackAsync(cancellationToken);
                    ex.ReThrow();
                }
            }
        }
        public async Task SetSettingValues(string settingGroupName, List<SettingValue> settingValues, CancellationToken cancellationToken = default)
        {
            using (var uow = await _unitOfWork.BeginTransactionAsync(cancellationToken))
            {
                try
                {
                    var settingGroup = await _settingGroupRepository.FindAsync(a => a.Name == settingGroupName, cancellationToken);
                    if (settingGroup is null)
                        settingGroup = await _settingGroupRepository.InsertAsync(new SettingGroup { Id = _snowflakeIdGenerator.Create(), Name = settingGroupName, NormalizedName = settingGroupName.ToUpper() }, true, cancellationToken: cancellationToken);

                    foreach (var settingValue in settingValues)
                    {
                        CheckSettingValueType(settingValue.Value, settingValue.ValueType);

                        var sv = await _settingValueRepository.FindAsync(a => a.SettingGroupId == settingGroup.Id && a.Id == settingValue.Id, cancellationToken);
                        if (sv is null)
                        {
                            settingValue.Id = _snowflakeIdGenerator.Create();
                            settingValue.SettingGroupId = settingGroup.Id;
                            await _settingValueRepository.InsertAsync(settingValue, cancellationToken: cancellationToken);
                        }
                        else
                            await _settingValueRepository.UpdateAsync(settingValue, cancellationToken: cancellationToken);
                    }
                    
                    await uow.CommitAsync(cancellationToken);
                    await _distributedEventBus.PublishAsync(new UpdateSettingEventData() { GroupName = settingGroupName, SettingScope = settingValues.First().SettingScope, SettingScopeKey = settingValues.First().SettingScopeKey });
                }
                catch (Exception ex)
                {
                    await uow.RollbackAsync(cancellationToken);
                    ex.ReThrow();
                }
            }
        }

        private void CheckSettingValueType(string settingValue, SettingValueType settingValueType)
        {
            switch (settingValueType)
            {
                case SettingValueType.String:
                case SettingValueType.JsonObject:
                    return;
                case SettingValueType.Bool:
                    if(bool.TryParse(settingValue, out var _))
                    {
                        return;
                    }
                    else
                    {
                        throw new ArgumentException($"SettingValue: {settingValue} Can Not Parse To Bool Type");
                    }
                case SettingValueType.Int:
                    if (int.TryParse(settingValue, out var _))
                    {
                        return;
                    }
                    else
                    {
                        throw new ArgumentException($"SettingValue: {settingValue} Can Not Parse To Int Type");
                    }
                case SettingValueType.Long:
                    if (long.TryParse(settingValue, out var _))
                    {
                        return;
                    }
                    else
                    {
                        throw new ArgumentException($"SettingValue: {settingValue} Can Not Parse To Long Type");
                    }
                case SettingValueType.Double:
                    if (double.TryParse(settingValue, out var _))
                    {
                        return;
                    }
                    else
                    {
                        throw new ArgumentException($"SettingValue: {settingValue} Can Not Parse To Double Type");
                    }
                case SettingValueType.Decimal:
                    if (decimal.TryParse(settingValue, out var _))
                    {
                        return;
                    }
                    else
                    {
                        throw new ArgumentException($"SettingValue: {settingValue} Can Not Parse To Decimal Type");
                    }
            }
        }
    }
}

这里CheckSettingValueType就是根据SettingValueType做数据校验,如果不符合条件的则拒绝修改。
就这样,数据库的设置管理操作基本完成。

SettingDefinition

数据库完成之后,接下来就是业务层面的事情了,这里我们定义一个ISettingDefinition接口,用作设置组结构的基本定义和作用范围,比如我们邮箱设置里面包含什么参数值,类型,默认值是什么。
ISettingDefinition:

using Wheel.DependencyInjection;
using Wheel.Enums;

namespace Wheel.Settings
{
    public interface ISettingDefinition : ITransientDependency
    {
        string GroupName { get; }
        SettingScope SettingScope { get; }

        ValueTask<Dictionary<string, SettingValueParams>> Define();
    }
}

EmailSettingDefinition:

using Wheel.Enums;

namespace Wheel.Settings.Email
{
    public class EmailSettingDefinition : ISettingDefinition
    {
        public string GroupName => "EmailSetting";

        public SettingScope SettingScope => SettingScope.Golbal;

        public ValueTask<Dictionary<string, SettingValueParams>> Define()
        {
            return ValueTask.FromResult(new Dictionary<string, SettingValueParams>
            {
                { "SenderName", new(SettingValueType.String, "Wheel") },
                { "Host", new(SettingValueType.String, "smtp.exmail.qq.com") },
                { "Prot", new(SettingValueType.Int, "465") },
                { "UserName", new(SettingValueType.String) },
                { "Password", new(SettingValueType.String) },
                { "UseSsl", new(SettingValueType.Bool, "true") },
            });
        }
    }
}
public record SettingValueParams(SettingValueType SettingValueType, string? DefalutValue = null, string? SettingScopeKey = null);

可以看到这里邮件的设置定义:
GroupName指定是EmailSetting这个分组。
SettingScope指定了是全局范围的设置。
SettingValueParams是一个record结构,包含设置值的类型,默认值以及范围的Key值。
Define里面是一个字典结构,里面定义的邮件发送设置里面所需要的所有参数以及默认值。

SettingDefinition的作用更多在于当数据库没有存储数据时,作为一个默认的结构以及默认值取用。

SettingManageAppService

接下来就需要提供API给客户端交互了,两个接口即可满足,一个用于获取设置,一个用于修改设置。
ISettingManageAppService:

using Wheel.Core.Dto;
using Wheel.DependencyInjection;
using Wheel.Enums;
using Wheel.Services.SettingManage.Dtos;

namespace Wheel.Services.SettingManage
{
    public interface ISettingManageAppService : ITransientDependency
    {
        Task<R<List<SettingGroupDto>>> GetAllSettingGroup(SettingScope settingScope = SettingScope.Golbal);
        Task<R> UpdateSettings(SettingGroupDto settingGroupDto, SettingScope settingScope = SettingScope.Golbal);
    }
}

SettingManageAppService:

using Wheel.Core.Dto;
using Wheel.Domain.Settings;
using Wheel.Domain;
using Wheel.Enums;
using Wheel.Services.SettingManage.Dtos;
using Wheel.Settings;

namespace Wheel.Services.SettingManage
{
    public class SettingManageAppService : WheelServiceBase, ISettingManageAppService
    {
        private readonly IBasicRepository<SettingGroup, long> _settingGroupRepository;
        private readonly IBasicRepository<SettingValue, long> _settingValueRepository;
        private readonly SettingManager _settingManager;

        public SettingManageAppService(IBasicRepository<SettingGroup, long> settingGroupRepository, IBasicRepository<SettingValue, long> settingValueRepository, SettingManager settingManager)
        {
            _settingGroupRepository = settingGroupRepository;
            _settingValueRepository = settingValueRepository;
            _settingManager = settingManager;
        }

        public async Task<R<List<SettingGroupDto>>> GetAllSettingGroup(SettingScope settingScope = SettingScope.Golbal)
        {
            var settingDefinitions = ServiceProvider.GetServices<ISettingDefinition>().Where(a => a.SettingScope == settingScope);
            var settingGroups = await _settingGroupRepository.GetListAsync(a => a.SettingValues.Any(a => a.SettingScope == settingScope && (settingScope == SettingScope.User ? a.SettingScopeKey == CurrentUser.Id : a.SettingScopeKey == null)));
            foreach (var settingDefinition in settingDefinitions)
            {
                if (settingGroups.Any(a => a.Name == settingDefinition.GroupName))
                    continue;
                else
                {
                    var group = new SettingGroup
                    {
                        Name = settingDefinition.GroupName,
                        NormalizedName = settingDefinition.GroupName.ToUpper(),
                        SettingValues = new List<SettingValue>()
                    };
                    foreach (var settings in await settingDefinition.Define())
                    {
                        group.SettingValues.Add(new SettingValue 
                        {
                            Key = settings.Key,
                            Value = settings.Value.DefalutValue,
                            ValueType = settings.Value.SettingValueType,
                            SettingScopeKey = settings.Value.SettingScopeKey,
                            SettingScope = settingScope
                        });
                    }
                    settingGroups.Add(group);
                }
            }
            var settingGroupDtos = Mapper.Map<List<SettingGroupDto>>(settingGroups);
            return new R<List<SettingGroupDto>>(settingGroupDtos);
        }

        public async Task<R> UpdateSettings(SettingGroupDto settingGroupDto, SettingScope settingScope = SettingScope.Golbal)
        {
            var settings = Mapper.Map<List<SettingValue>>(settingGroupDto.SettingValues);
            settings.ForEach(a =>
            {
                a.SettingScope = settingScope;
                a.SettingScopeKey = settingScope == SettingScope.User ? CurrentUser.Id : null;
            });
            await _settingManager.SetSettingValues(settingGroupDto.Name, settings);
            return new R();
        }
    }
}

这里可以看到GetAllSettingGroup的实现,当数据库取值没有改设置组数据时,获取SettingDefinition的结构返回给客户端。

SettingManageController

SettingManageController很简单,就是包装ISettingManageAppService暴露API出去即可。

using Microsoft.AspNetCore.Mvc;
using Wheel.Core.Dto;
using Wheel.Enums;
using Wheel.Services.SettingManage;
using Wheel.Services.SettingManage.Dtos;

namespace Wheel.Controllers
{
    /// <summary>
    /// 设置管理
    /// </summary>
    [Route("api/[controller]")]
    [ApiController]
    public class SettingManageController : WheelControllerBase
    {
        private readonly ISettingManageAppService _settingManageAppService;

        public SettingManageController(ISettingManageAppService settingManageAppService)
        {
            _settingManageAppService = settingManageAppService;
        }

        /// <summary>
        /// 获取所有设置
        /// </summary>
        /// <param name="settingScope">设置范围</param>
        /// <returns></returns>
        [HttpGet()]
        public Task<R<List<SettingGroupDto>>> GetAllSettingGroup(SettingScope settingScope = SettingScope.Golbal)
        {
            return _settingManageAppService.GetAllSettingGroup(settingScope);
        }
        /// <summary>
        /// 更新设置
        /// </summary>
        /// <param name="settingGroupDto">设置组数据</param>
        /// <param name="settingScope">设置范围</param>
        /// <returns></returns>
        [HttpPut("{settingScope}")]
        public Task<R> UpdateSettings(SettingGroupDto settingGroupDto, SettingScope settingScope)
        {
            return _settingManageAppService.UpdateSettings(settingGroupDto, settingScope);
        }
    }
}

就这样与客户端的交互API完成了。

SettingProvider

接下来则是需要实现一个给内部业务获取设置的工具。
SettingProvider用作程序内获取对应设置。直接封装获取全局设置或用户设置。

using Wheel.DependencyInjection;

namespace Wheel.Settings
{
    public interface ISettingProvider : ITransientDependency
    {
        public Task<Dictionary<string, string>> GetGolbalSettings(string groupKey, CancellationToken cancellationToken = default);
        public Task<string> GetGolbalSetting(string groupKey, string settingKey, CancellationToken cancellationToken = default);
        public Task<T> GetGolbalSetting<T>(string groupKey, string settingKey, CancellationToken cancellationToken = default) where T : struct;

        public Task<Dictionary<string, string>> GetUserSettings(string groupKey, CancellationToken cancellationToken = default);
        public Task<string> GetUserSetting(string groupKey, string settingKey, CancellationToken cancellationToken = default);
        public Task<T> GetUserSetting<T>(string groupKey, string settingKey, CancellationToken cancellationToken = default) where T : struct;
    }
}
using Microsoft.Extensions.Caching.Distributed;
using Wheel.Core.Users;
using Wheel.Domain.Settings;
using Wheel.Enums;

namespace Wheel.Settings
{
    public class DefaultSettingProvider : ISettingProvider
    {
        private readonly SettingManager _settingManager;
        private readonly IDistributedCache _distributedCache;
        private readonly ICurrentUser _currentUser;
        private readonly IServiceProvider _serviceProvider;

        public DefaultSettingProvider(SettingManager settingManager, IDistributedCache distributedCache, ICurrentUser currentUser, IServiceProvider serviceProvider)
        {
            _settingManager = settingManager;
            _distributedCache = distributedCache;
            _currentUser = currentUser;
            _serviceProvider = serviceProvider;
        }

        public async Task<string> GetGolbalSetting(string groupKey, string settingKey, CancellationToken cancellationToken = default)
        {
            var settings = await GetGolbalSettings(groupKey, cancellationToken);
            return settings[settingKey];
        }

        public async Task<T> GetGolbalSetting<T>(string groupKey, string settingKey, CancellationToken cancellationToken = default) where T : struct
        {
            var settings = await GetGolbalSettings(groupKey, cancellationToken);
            return settings[settingKey].To<T>();
        }

        public async Task<Dictionary<string, string>> GetGolbalSettings(string groupKey, CancellationToken cancellationToken = default)
        {
            var cacheSettings = await GetCacheItem(groupKey, SettingScope.Golbal, cancellationToken: cancellationToken);
            if(cacheSettings is null)
            {
                var dbSettings = await _settingManager.GetSettingValues(groupKey, SettingScope.Golbal, cancellationToken: cancellationToken);
                if(dbSettings is null)
                {
                    var settingDefinition = _serviceProvider.GetServices<ISettingDefinition>().FirstOrDefault(a => a.GroupName == groupKey && a.SettingScope == SettingScope.Golbal);
                    if(settingDefinition is null)
                        return new();
                    else
                    {
                        var setting = await settingDefinition.Define();
                        return setting.ToDictionary(a => a.Key, a => a.Value.DefalutValue)!;
                    }
                }
                return dbSettings.ToDictionary(a => a.Key, a => a.Value);
            }
            else
            {
                return cacheSettings.ToDictionary(a => a.Key, a => a.Value);
            }
        }

        public async Task<string> GetUserSetting(string groupKey, string settingKey, CancellationToken cancellationToken = default)
        {
            var settings = await GetUserSettings(groupKey, cancellationToken);
            return settings[settingKey];
        }

        public async Task<T> GetUserSetting<T>(string groupKey, string settingKey, CancellationToken cancellationToken = default) where T : struct
        {
            var settings = await GetUserSettings(groupKey, cancellationToken);
            return settings[settingKey].To<T>();
        }

        public async Task<Dictionary<string, string>> GetUserSettings(string groupKey, CancellationToken cancellationToken = default)
        {
            var cacheSettings = await GetCacheItem(groupKey, SettingScope.User, settingScopeKey: _currentUser.Id, cancellationToken: cancellationToken);
            if (cacheSettings is null)
            {
                var dbSettings = await _settingManager.GetSettingValues(groupKey, SettingScope.User, settingScopeKey: _currentUser.Id, cancellationToken: cancellationToken);
                if (dbSettings is null)
                {
                    var settingDefinition = _serviceProvider.GetServices<ISettingDefinition>().FirstOrDefault(a => a.GroupName == groupKey && a.SettingScope == SettingScope.User);
                    if (settingDefinition is null)
                        return new();
                    else
                    {
                        var setting = await settingDefinition.Define();
                        return setting.ToDictionary(a => a.Key, a => a.Value.DefalutValue)!;
                    }
                }
                return dbSettings.ToDictionary(a => a.Key, a => a.Value);
            }
            else
            {
                return cacheSettings.ToDictionary(a => a.Key, a => a.Value);
            }
        }
        private async Task<List<SettingValueCacheItem>> GetCacheItem(string groupKey, SettingScope settingScope, string settingScopeKey = null, CancellationToken cancellationToken = default)
        {
            var cacheKey = BuildCacheKey(groupKey, settingScope, settingScopeKey);
            return await _distributedCache.GetAsync<List<SettingValueCacheItem>>(cacheKey, cancellationToken);
        }
        private string BuildCacheKey(string groupKey, SettingScope settingScope, string settingScopeKey)
        {
            return $"{groupKey}:{settingScope}"+ (settingScope == SettingScope.Golbal ? "" : $":{settingScopeKey}");
        }
    }
}

using Wheel.Domain.Settings;
using Wheel.Enums;

namespace Wheel.Settings
{
    public class SettingValueCacheItem
    {
        public string Key { get; set; }
        public string Value { get; set; }
        public SettingValueType ValueType { get; set; }
    }
}

这里获取设置时优先从缓存读取,若缓存没有,则取数据库,若数据库再没有,则从SettingDefintion中获取默认值。
那么这里缓存数据从哪里来呢?细心的可以看到上面的SettingManager的修改设置的方法中有一行代码:

await _distributedEventBus.PublishAsync(new UpdateSettingEventData() { GroupName = settingGroupName, SettingScope = settingValues.First().SettingScope, SettingScopeKey = settingValues.First().SettingScopeKey });

这里通过消息队列,通知更新缓存。

UpdateSettingEvent

using Wheel.Enums;

namespace Wheel.EventBus.EventDatas
{
    [EventName("UpdateSetting")]
    public class UpdateSettingEventData
    {
        public string GroupName { get; set; }
        public SettingScope SettingScope { get; set; }
        public string? SettingScopeKey { get; set; }
    }
}
using AutoMapper;
using Microsoft.Extensions.Caching.Distributed;
using Wheel.DependencyInjection;
using Wheel.Domain.Settings;
using Wheel.EventBus.Distributed;
using Wheel.EventBus.EventDatas;
using Wheel.Services.SettingManage.Dtos;

namespace Wheel.EventBus.Handlers
{
    public class UpdateSettingEventHandler : IDistributedEventHandler<UpdateSettingEventData>, ITransientDependency
    {

        private readonly SettingManager _settingManager;
        private readonly IDistributedCache _distributedCache;
        private readonly IMapper _mapper;

        public UpdateSettingEventHandler(SettingManager settingManager, IDistributedCache distributedCache, IMapper mapper)
        {
            _settingManager = settingManager;
            _distributedCache = distributedCache;
            _mapper = mapper;
        }

        public async Task Handle(UpdateSettingEventData eventData, CancellationToken cancellationToken = default)
        {
            var settings = await _settingManager.GetSettingValues(eventData.GroupName, eventData.SettingScope, eventData.SettingScopeKey, cancellationToken);
            
            await _distributedCache.SetAsync($"Setting:{eventData.GroupName}:{eventData.SettingScope}" + (eventData.SettingScope == Enums.SettingScope.Golbal ? "" : $":{eventData.SettingScopeKey}"), _mapper.Map<List<SettingValueDto>>(settings));
        }
    }
}

UpdateSettingEventHandler负责在设置更新之后,获取最新的设置直接塞到缓存当中。
只需一行代码将SettingProvider加入到WheelServiceBase和WheelControllerBase中,后续就可以很方便的获取设置,不需要频繁在构造器注入:

public ISettingProvider SettingProvider => LazyGetService<ISettingProvider>();

测试

启动程序,测试一下获取设置值,这里可以看到,我们通过SettingProvider成功读取了设置。

image.png
就这样,我们完成了我们的设置管理功能。

轮子仓库地址https://github.com/Wheel-Framework/Wheel
欢迎进群催更。

image.png

posted @ 2023-10-19 11:44  饭勺oO  阅读(485)  评论(1编辑  收藏  举报