mi-assessment/server/MiAssessment/src/MiAssessment.Admin.Business/Services/AdminConfigService.cs
zpc 8489b4300c refactor(config): 统一配置读取架构,运营配置从Admin库读取
- Model层新增AdminConfig实体和AdminConfigReadDbContext(只读连接Admin库)
- API项目新增AdminConnection连接字符串,注册AdminConfigReadDbContext
- Core层ConfigService按key路由:运营配置走Admin库,业务配置走业务库
- WechatPayConfigService改为从Admin库读取支付/小程序配置
- WechatService新增AdminConfigReadDbContext注入,配置读取改为Admin库
- Autofac注册同步更新三个服务的依赖注入
- Admin.Business的AdminConfigService改用AdminConfigDbContext连接Admin库
2026-02-20 15:48:16 +08:00

378 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
/// <summary>
/// 后台配置服务实现
/// 从 Admin 数据库读取配置
/// </summary>
public class AdminConfigService : IAdminConfigService
{
private readonly AdminConfigDbContext _adminDbContext;
private readonly IRedisService _redisService;
private readonly ILogger<AdminConfigService> _logger;
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = false
};
public AdminConfigService(
AdminConfigDbContext adminDbContext,
IRedisService redisService,
ILogger<AdminConfigService> logger)
{
_adminDbContext = adminDbContext;
_redisService = redisService;
_logger = logger;
}
/// <inheritdoc />
public async Task<T?> GetConfigAsync<T>(string key) where T : class
{
var jsonValue = await GetConfigRawAsync(key);
if (string.IsNullOrEmpty(jsonValue))
{
return null;
}
try
{
return JsonSerializer.Deserialize<T>(jsonValue, JsonOptions);
}
catch (JsonException ex)
{
_logger.LogWarning(ex, "Failed to deserialize config {Key}", key);
return null;
}
}
/// <inheritdoc />
public async Task<string?> 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;
}
/// <inheritdoc />
public async Task<bool> UpdateConfigAsync<T>(string key, T config) where T : class
{
var jsonValue = JsonSerializer.Serialize(config, JsonOptions);
return await UpdateConfigRawAsync(key, jsonValue);
}
/// <inheritdoc />
public async Task<bool> 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;
}
/// <inheritdoc />
public Task<string?> 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<string?>(null)
};
}
catch (JsonException)
{
return Task.FromResult<string?>("配置格式无效请提供有效的JSON数据");
}
}
/// <inheritdoc />
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
/// <summary>
/// 验证支付宝配置
/// </summary>
private string? ValidateAlipaySetting(string jsonValue)
{
try
{
var setting = JsonSerializer.Deserialize<AlipaySetting>(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 "支付宝配置格式无效";
}
}
/// <summary>
/// 验证微信支付配置
/// </summary>
private string? ValidateWeixinPaySetting(string jsonValue)
{
try
{
var setting = JsonSerializer.Deserialize<WeixinPaySetting>(jsonValue, JsonOptions);
if (setting?.Merchants == null || setting.Merchants.Count == 0)
{
return null; // 空配置是允许的
}
var prefixes = new HashSet<string>();
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 "微信支付配置格式无效";
}
}
/// <summary>
/// 验证小程序配置
/// </summary>
private string? ValidateMiniprogramSetting(string jsonValue)
{
try
{
var setting = JsonSerializer.Deserialize<MiniprogramSetting>(jsonValue, JsonOptions);
if (setting?.Miniprograms == null || setting.Miniprograms.Count == 0)
{
return null; // 空配置是允许的
}
var hasDefault = false;
var prefixes = new HashSet<string>();
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 "小程序配置格式无效";
}
}
/// <summary>
/// 验证H5配置
/// </summary>
private string? ValidateH5Setting(string jsonValue)
{
try
{
var setting = JsonSerializer.Deserialize<H5Setting>(jsonValue, JsonOptions);
if (setting?.H5Apps == null || setting.H5Apps.Count == 0)
{
return null; // 空配置是允许的
}
var hasDefault = false;
var prefixes = new HashSet<string>();
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
}