From f5fa95031e039533c9ba92772384a0015df5a9e7 Mon Sep 17 00:00:00 2001 From: GZTime Date: Sun, 28 Aug 2022 05:03:47 +0800 Subject: [PATCH] feat: config manager --- GZCTF.Test/ConfigServiceTest.cs | 2 - GZCTF/Controllers/AccountController.cs | 4 +- GZCTF/Controllers/AdminController.cs | 51 ++++++++++++----- GZCTF/Controllers/InfoController.cs | 7 +-- GZCTF/Program.cs | 1 + GZCTF/Services/ConfigService.cs | 66 ++++++++++++++-------- GZCTF/Services/Interface/IConfigService.cs | 24 ++++++++ 7 files changed, 106 insertions(+), 49 deletions(-) create mode 100644 GZCTF/Services/Interface/IConfigService.cs diff --git a/GZCTF.Test/ConfigServiceTest.cs b/GZCTF.Test/ConfigServiceTest.cs index 6d586fc63..3537f70e6 100644 --- a/GZCTF.Test/ConfigServiceTest.cs +++ b/GZCTF.Test/ConfigServiceTest.cs @@ -22,9 +22,7 @@ public void TestGetConfigs() Assert.True(configs.Count > 0); foreach (var config in configs) - { output.WriteLine($"{config.ConfigKey,-32}={config.Value}"); - } } } diff --git a/GZCTF/Controllers/AccountController.cs b/GZCTF/Controllers/AccountController.cs index ea6fcfac0..c01aa76ae 100644 --- a/GZCTF/Controllers/AccountController.cs +++ b/GZCTF/Controllers/AccountController.cs @@ -27,14 +27,14 @@ public class AccountController : ControllerBase private readonly IFileRepository fileService; private readonly IRecaptchaExtension recaptcha; private readonly IHostEnvironment environment; - private readonly IOptions accountPolicy; + private readonly IOptionsSnapshot accountPolicy; public AccountController( IMailSender _mailSender, IFileRepository _FileService, IHostEnvironment _environment, IRecaptchaExtension _recaptcha, - IOptions _accountPolicy, + IOptionsSnapshot _accountPolicy, UserManager _userManager, SignInManager _signInManager, ILogger _logger) diff --git a/GZCTF/Controllers/AdminController.cs b/GZCTF/Controllers/AdminController.cs index 1993d52df..25ee12ad0 100644 --- a/GZCTF/Controllers/AdminController.cs +++ b/GZCTF/Controllers/AdminController.cs @@ -4,6 +4,7 @@ using CTFServer.Models.Request.Admin; using CTFServer.Models.Request.Teams; using CTFServer.Repositories.Interface; +using CTFServer.Services.Interface; using CTFServer.Utils; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; @@ -27,24 +28,24 @@ public class AdminController : ControllerBase private readonly UserManager userManager; private readonly ILogRepository logRepository; private readonly IFileRepository fileService; - private readonly IConfiguration configuration; + private readonly IConfigService configService; private readonly ITeamRepository teamRepository; private readonly IGameRepository gameRepository; - private readonly IServiceScopeFactory serviceProvider; + private readonly IServiceProvider serviceProvider; private readonly IParticipationRepository participationRepository; public AdminController(UserManager _userManager, IFileRepository _FileService, ILogRepository _logRepository, - IConfiguration _configuration, + IConfigService _configService, ITeamRepository _teamRepository, IGameRepository _gameRepository, - IServiceScopeFactory _serviceProvider, + IServiceProvider _serviceProvider, IParticipationRepository _participationRepository) { userManager = _userManager; fileService = _FileService; - configuration = _configuration; + configService = _configService; logRepository = _logRepository; teamRepository = _teamRepository; gameRepository = _gameRepository; @@ -52,26 +53,46 @@ public AdminController(UserManager _userManager, participationRepository = _participationRepository; } + /// + /// 获取配置 + /// + /// + /// 使用此接口获取全局设置,需要Admin权限 + /// + /// 全局配置 + /// 未授权用户 + /// 禁止访问 + [HttpGet("Config")] + [ProducesResponseType(typeof(GlobalConfig), StatusCodes.Status200OK)] + public IActionResult GetConfigs() + { + GlobalConfig config = new() + { + AccoutPolicy = serviceProvider.GetRequiredService>().Value + }; + + return Ok(config); + } + /// /// 更改配置 /// /// /// 使用此接口更改全局设置,需要Admin权限 /// - /// 用户列表 + /// 更新成功 /// 未授权用户 /// 禁止访问 [HttpPut("Config")] - [ProducesResponseType(typeof(UserInfoModel[]), StatusCodes.Status200OK)] - public async Task UpdateConfigs(/*[FromBody] GlobalConfig model*/) + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task UpdateConfigs([FromBody] GlobalConfig model, CancellationToken token) { - await using var scope = serviceProvider.CreateAsyncScope(); - - var accountPolicy = scope.ServiceProvider.GetRequiredService>(); - - Console.WriteLine($"Active={accountPolicy.Value.ActiveOnRegister}"); - accountPolicy.Value.ActiveOnRegister = false; - Console.WriteLine($"Active={accountPolicy.Value.ActiveOnRegister}"); + foreach(var prop in typeof(GlobalConfig).GetProperties()) + { + var value = prop.GetValue(model); + if (value is not null) + await configService.SaveConfig(prop.PropertyType, value, token); + } return Ok(); } diff --git a/GZCTF/Controllers/InfoController.cs b/GZCTF/Controllers/InfoController.cs index 324de09e5..f711f67d1 100644 --- a/GZCTF/Controllers/InfoController.cs +++ b/GZCTF/Controllers/InfoController.cs @@ -13,17 +13,14 @@ namespace CTFServer.Controllers; [ApiController] public class InfoController : ControllerBase { - private readonly ILogger logger; - private readonly IOptions accountPolicy; + private readonly IOptionsSnapshot accountPolicy; private readonly INoticeRepository noticeRepository; private readonly IRecaptchaExtension recaptchaExtension; public InfoController(INoticeRepository _noticeRepository, IRecaptchaExtension _recaptchaExtension, - IOptions _accountPolicy, - ILogger _logger) + IOptionsSnapshot _accountPolicy) { - logger = _logger; accountPolicy = _accountPolicy; noticeRepository = _noticeRepository; recaptchaExtension = _recaptchaExtension; diff --git a/GZCTF/Program.cs b/GZCTF/Program.cs index 0a76fad5b..c562764a9 100644 --- a/GZCTF/Program.cs +++ b/GZCTF/Program.cs @@ -187,6 +187,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddChannel(); builder.Services.AddHostedService(); diff --git a/GZCTF/Services/ConfigService.cs b/GZCTF/Services/ConfigService.cs index 5804caefa..6f35b96f8 100644 --- a/GZCTF/Services/ConfigService.cs +++ b/GZCTF/Services/ConfigService.cs @@ -1,27 +1,22 @@ -using System; -using System.ComponentModel; -using System.IO; -using System.Reflection; +using System.ComponentModel; using CTFServer.Models.Data; -using IdentityModel.OidcClient; +using CTFServer.Services.Interface; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.IdentityModel.Tokens; using NPOI.SS.Formula.Functions; +using YamlDotNet.Core.Tokens; namespace CTFServer.Services; -public class ConfigService : IHostedService +public class ConfigService : IConfigService { - private readonly ILogger logger; - private readonly IServiceScopeFactory serviceScopeFactory; - private HashSet Data = new(); + private readonly IConfigurationRoot? configuration; + private readonly AppDbContext context; - public ConfigService(IServiceScopeFactory provider, ILogger _logger) + public ConfigService(AppDbContext _context, + IConfiguration _configuration) { - serviceScopeFactory = provider; - logger = _logger; + context = _context; + configuration = _configuration as IConfigurationRoot; } private static void GetConfigsInternal(string key, HashSet configs, Type? type, object? value) @@ -44,6 +39,16 @@ private static void GetConfigsInternal(string key, HashSet configs, Type } } + public static HashSet GetConfigs(Type type, object? value) + { + HashSet configs = new(); + + foreach (var item in type.GetProperties()) + GetConfigsInternal($"{type.Name}:{item.Name}", configs, item.PropertyType, item.GetValue(value)); + + return configs; + } + public static HashSet GetConfigs(T config) where T : class { HashSet configs = new(); @@ -55,21 +60,32 @@ public static HashSet GetConfigs(T config) where T : class return configs; } - public void SaveConfig(T config) where T : class - { - } + public Task SaveConfig(Type type, object? value, CancellationToken token = default) + => SaveConfigInternal(GetConfigs(type, value), token); + + public Task SaveConfig(T config, CancellationToken token = default) where T : class + => SaveConfigInternal(GetConfigs(config), token); - public async Task StartAsync(CancellationToken cancellationToken) + private async Task SaveConfigInternal(HashSet configs, CancellationToken token = default) { - await using var scope = serviceScopeFactory.CreateAsyncScope(); - var context = scope.ServiceProvider.GetRequiredService(); + var dbConfigs = await context.Configs.ToDictionaryAsync(c => c.ConfigKey, c => c, token); + foreach (var conf in configs) + { + if (dbConfigs.TryGetValue(conf.ConfigKey, out var dbConf)) + { + if (dbConf.Value != conf.Value) + dbConf.Value = conf.Value; + } + else + { + await context.Configs.AddAsync(conf, token); + } + } - var configs = await context.Configs.AsNoTracking().ToListAsync(cancellationToken); - Data = configs.ToHashSet(); + await context.SaveChangesAsync(token); + configuration?.Reload(); } - public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; - private static bool IsArrayLikeInterface(Type type) { if (!type.IsInterface || !type.IsConstructedGenericType) { return false; } diff --git a/GZCTF/Services/Interface/IConfigService.cs b/GZCTF/Services/Interface/IConfigService.cs new file mode 100644 index 000000000..5378a0a43 --- /dev/null +++ b/GZCTF/Services/Interface/IConfigService.cs @@ -0,0 +1,24 @@ +using System; +namespace CTFServer.Services.Interface; + +public interface IConfigService +{ + /// + /// 保存配置对象 + /// + /// 选项类型 + /// 选项对象 + /// + /// + public Task SaveConfig(T config, CancellationToken token = default) where T : class; + + /// + /// 保存配置对象 + /// + /// 对象类型 + /// 对象值 + /// + /// + public Task SaveConfig(Type type, object? value, CancellationToken token = default); +} +