using System.Text.Json; using MiAssessment.Admin.Business.Data; using MiAssessment.Admin.Business.Entities; using MiAssessment.Admin.Business.Models; using MiAssessment.Admin.Business.Models.Config; using MiAssessment.Admin.Business.Services.Interfaces; using MiAssessment.Core.Interfaces; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace MiAssessment.Admin.Business.Services; /// /// 后台配置服务实现 /// 从 Admin 数据库读取配置 /// public class AdminConfigService : IAdminConfigService { private readonly AdminConfigDbContext _adminDbContext; private readonly IRedisService _redisService; private readonly ILogger _logger; private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = false }; public AdminConfigService( AdminConfigDbContext adminDbContext, IRedisService redisService, ILogger logger) { _adminDbContext = adminDbContext; _redisService = redisService; _logger = logger; } /// public async Task GetConfigAsync(string key) where T : class { var jsonValue = await GetConfigRawAsync(key); if (string.IsNullOrEmpty(jsonValue)) { return null; } try { return JsonSerializer.Deserialize(jsonValue, JsonOptions); } catch (JsonException ex) { _logger.LogWarning(ex, "Failed to deserialize config {Key}", key); return null; } } /// public async Task GetConfigRawAsync(string key) { // 尝试从缓存获取 var cacheKey = GetCacheKey(key); try { var cachedValue = await _redisService.GetStringAsync(cacheKey); if (!string.IsNullOrEmpty(cachedValue)) { return cachedValue; } } catch (Exception ex) { _logger.LogWarning(ex, "Failed to get config from cache: {Key}", key); } // 从数据库获取 var config = await _adminDbContext.AdminConfigs .Where(c => c.ConfigKey == key) .Select(c => c.ConfigValue) .FirstOrDefaultAsync(); // 存入缓存(10分钟过期) if (!string.IsNullOrEmpty(config)) { try { await _redisService.SetStringAsync(cacheKey, config, TimeSpan.FromMinutes(10)); } catch (Exception ex) { _logger.LogWarning(ex, "Failed to cache config: {Key}", key); } } return config; } /// public async Task UpdateConfigAsync(string key, T config) where T : class { var jsonValue = JsonSerializer.Serialize(config, JsonOptions); return await UpdateConfigRawAsync(key, jsonValue); } /// public async Task UpdateConfigRawAsync(string key, string jsonValue) { // 验证配置 var validationError = await ValidateConfigAsync(key, jsonValue); if (!string.IsNullOrEmpty(validationError)) { throw new BusinessException(BusinessErrorCodes.ValidationFailed, validationError); } // 查找现有配置 var existingConfig = await _adminDbContext.AdminConfigs .FirstOrDefaultAsync(c => c.ConfigKey == key); if (existingConfig != null) { // 更新现有配置 existingConfig.ConfigValue = jsonValue; existingConfig.UpdatedAt = DateTime.Now; } else { // 创建新配置 var newConfig = new AdminConfig { ConfigKey = key, ConfigValue = jsonValue, CreatedAt = DateTime.Now }; _adminDbContext.AdminConfigs.Add(newConfig); } var result = await _adminDbContext.SaveChangesAsync() > 0; // 清理缓存 if (result) { await ClearConfigCacheAsync(key); } return result; } /// public Task ValidateConfigAsync(string key, string jsonValue) { try { // 验证JSON格式 using var doc = JsonDocument.Parse(jsonValue); // 根据配置键进行特定验证 return key switch { ConfigKeys.WeixinPaySetting => Task.FromResult(ValidateWeixinPaySetting(jsonValue)), ConfigKeys.AlipayPaySetting => Task.FromResult(ValidateAlipaySetting(jsonValue)), ConfigKeys.MiniprogramSetting => Task.FromResult(ValidateMiniprogramSetting(jsonValue)), ConfigKeys.H5Setting => Task.FromResult(ValidateH5Setting(jsonValue)), _ => Task.FromResult(null) }; } catch (JsonException) { return Task.FromResult("配置格式无效,请提供有效的JSON数据"); } } /// public async Task ClearConfigCacheAsync(string key) { var cacheKey = GetCacheKey(key); try { await _redisService.DeleteAsync(cacheKey); _logger.LogInformation("Cleared config cache: {Key}", key); } catch (Exception ex) { _logger.LogWarning(ex, "Failed to clear config cache: {Key}", key); } } #region Private Validation Methods /// /// 验证支付宝配置 /// private string? ValidateAlipaySetting(string jsonValue) { try { var setting = JsonSerializer.Deserialize(jsonValue, JsonOptions); if (setting?.Merchants == null || setting.Merchants.Count == 0) { return null; // 空配置是允许的 } foreach (var merchant in setting.Merchants) { // 验证AppId必填 if (string.IsNullOrWhiteSpace(merchant.AppId)) { return $"商户\"{merchant.Name}\"的AppId不能为空"; } // 验证商户名称必填 if (string.IsNullOrWhiteSpace(merchant.Name)) { return "商户名称不能为空"; } } return null; } catch (JsonException) { return "支付宝配置格式无效"; } } /// /// 验证微信支付配置 /// private string? ValidateWeixinPaySetting(string jsonValue) { try { var setting = JsonSerializer.Deserialize(jsonValue, JsonOptions); if (setting?.Merchants == null || setting.Merchants.Count == 0) { return null; // 空配置是允许的 } var prefixes = new HashSet(); foreach (var merchant in setting.Merchants) { // 验证前缀长度 if (string.IsNullOrEmpty(merchant.OrderPrefix) || merchant.OrderPrefix.Length != 3) { return $"商户\"{merchant.Name}\"的前缀必须是3位字符"; } // 验证前缀唯一性 if (!prefixes.Add(merchant.OrderPrefix)) { return $"商户前缀\"{merchant.OrderPrefix}\"重复,每个商户的前缀必须唯一"; } } return null; } catch (JsonException) { return "微信支付配置格式无效"; } } /// /// 验证小程序配置 /// private string? ValidateMiniprogramSetting(string jsonValue) { try { var setting = JsonSerializer.Deserialize(jsonValue, JsonOptions); if (setting?.Miniprograms == null || setting.Miniprograms.Count == 0) { return null; // 空配置是允许的 } var hasDefault = false; var prefixes = new HashSet(); foreach (var miniprogram in setting.Miniprograms) { // 检查是否有默认小程序 if (miniprogram.IsDefault == 1) { hasDefault = true; } // 验证订单前缀(如果有) if (!string.IsNullOrEmpty(miniprogram.OrderPrefix)) { if (miniprogram.OrderPrefix.Length != 2) { return $"小程序\"{miniprogram.Name}\"的订单前缀必须是2位字符"; } if (!prefixes.Add(miniprogram.OrderPrefix)) { return $"订单前缀\"{miniprogram.OrderPrefix}\"重复,每个小程序的前缀必须唯一"; } } } if (!hasDefault) { return "请至少设置一个默认小程序"; } return null; } catch (JsonException) { return "小程序配置格式无效"; } } /// /// 验证H5配置 /// private string? ValidateH5Setting(string jsonValue) { try { var setting = JsonSerializer.Deserialize(jsonValue, JsonOptions); if (setting?.H5Apps == null || setting.H5Apps.Count == 0) { return null; // 空配置是允许的 } var hasDefault = false; var prefixes = new HashSet(); foreach (var h5app in setting.H5Apps) { // 检查是否有默认H5应用 if (h5app.IsDefault == 1) { hasDefault = true; } // 验证订单前缀(如果有) if (!string.IsNullOrEmpty(h5app.OrderPrefix)) { if (h5app.OrderPrefix.Length != 2) { return $"H5应用\"{h5app.Name}\"的订单前缀必须是2位字符"; } if (!prefixes.Add(h5app.OrderPrefix)) { return $"订单前缀\"{h5app.OrderPrefix}\"重复,每个H5应用的前缀必须唯一"; } } } if (!hasDefault) { return "请至少设置一个默认H5应用"; } return null; } catch (JsonException) { return "H5配置格式无效"; } } #endregion #region Private Helper Methods private static string GetCacheKey(string key) => $"config:{key}"; #endregion }