using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Security.Cryptography;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using TakeoutSaaS.Infrastructure.Common.Options;
using TakeoutSaaS.Shared.Abstractions.Data;
namespace TakeoutSaaS.Infrastructure.Common.Persistence;
///
/// 数据库连接工厂,支持读写分离及连接配置校验。
///
public sealed class DatabaseConnectionFactory(
IOptionsMonitor optionsMonitor,
IConfiguration configuration,
ILogger logger) : IDatabaseConnectionFactory
{
private const int DefaultCommandTimeoutSeconds = 30;
private const int DefaultMaxRetryCount = 3;
private const int DefaultMaxRetryDelaySeconds = 5;
///
/// 获取指定数据源与读写角色的连接信息。
///
/// 逻辑数据源名称。
/// 连接角色。
/// 连接串与超时/重试配置。
public DatabaseConnectionDetails GetConnection(string dataSourceName, DatabaseConnectionRole role)
{
if (string.IsNullOrWhiteSpace(dataSourceName))
{
logger.LogWarning("请求的数据源名称为空,使用默认连接。");
return BuildFallbackConnection();
}
var options = optionsMonitor.CurrentValue.Find(dataSourceName);
if (options != null)
{
if (!ValidateOptions(dataSourceName, options))
{
return BuildFallbackConnection();
}
var connectionString = ResolveConnectionString(options, role);
return new DatabaseConnectionDetails(
connectionString,
options.CommandTimeoutSeconds,
options.MaxRetryCount,
options.MaxRetryDelaySeconds);
}
var fallback = configuration.GetConnectionString(dataSourceName);
if (string.IsNullOrWhiteSpace(fallback))
{
logger.LogError("缺少数据源 {DataSource} 的连接配置,回退到默认本地连接。", dataSourceName);
return BuildFallbackConnection();
}
logger.LogWarning("未找到数据源 {DataSource} 的 Database 节配置,回退使用 ConnectionStrings。", dataSourceName);
return new DatabaseConnectionDetails(
fallback,
DefaultCommandTimeoutSeconds,
DefaultMaxRetryCount,
DefaultMaxRetryDelaySeconds);
}
///
/// 校验数据源配置完整性。
///
/// 数据源名称。
/// 数据源配置。
/// 配置不合法时抛出。
private bool ValidateOptions(string dataSourceName, DatabaseDataSourceOptions options)
{
var results = new List();
var context = new ValidationContext(options);
if (!Validator.TryValidateObject(options, context, results, validateAllProperties: true))
{
var errorMessages = string.Join("; ", results.Select(result => result.ErrorMessage));
logger.LogError("数据源 {DataSource} 配置非法:{Errors},回退到默认连接。", dataSourceName, errorMessages);
return false;
}
return true;
}
///
/// 根据读写角色选择连接串,从读连接随机分配。
///
/// 数据源配置。
/// 连接角色。
/// 可用连接串。
private string ResolveConnectionString(DatabaseDataSourceOptions options, DatabaseConnectionRole role)
{
if (role == DatabaseConnectionRole.Read && options.Reads.Count > 0)
{
var index = RandomNumberGenerator.GetInt32(options.Reads.Count);
return options.Reads[index];
}
if (string.IsNullOrWhiteSpace(options.Write))
{
return BuildFallbackConnection().ConnectionString;
}
return options.Write;
}
private DatabaseConnectionDetails BuildFallbackConnection()
{
const string fallback = "Host=120.53.222.17;Port=5432;Database=postgres;Username=postgres;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=1;Maximum Pool Size=20";
logger.LogWarning("使用默认回退连接串:{Connection}", fallback);
return new DatabaseConnectionDetails(
fallback,
DefaultCommandTimeoutSeconds,
DefaultMaxRetryCount,
DefaultMaxRetryDelaySeconds);
}
}