122 lines
4.7 KiB
C#
122 lines
4.7 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// 数据库连接工厂,支持读写分离及连接配置校验。
|
|
/// </summary>
|
|
public sealed class DatabaseConnectionFactory(
|
|
IOptionsMonitor<DatabaseOptions> optionsMonitor,
|
|
IConfiguration configuration,
|
|
ILogger<DatabaseConnectionFactory> logger) : IDatabaseConnectionFactory
|
|
{
|
|
private const int DefaultCommandTimeoutSeconds = 30;
|
|
private const int DefaultMaxRetryCount = 3;
|
|
private const int DefaultMaxRetryDelaySeconds = 5;
|
|
|
|
/// <summary>
|
|
/// 获取指定数据源与读写角色的连接信息。
|
|
/// </summary>
|
|
/// <param name="dataSourceName">逻辑数据源名称。</param>
|
|
/// <param name="role">连接角色。</param>
|
|
/// <returns>连接串与超时/重试配置。</returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 校验数据源配置完整性。
|
|
/// </summary>
|
|
/// <param name="dataSourceName">数据源名称。</param>
|
|
/// <param name="options">数据源配置。</param>
|
|
/// <exception cref="InvalidOperationException">配置不合法时抛出。</exception>
|
|
private bool ValidateOptions(string dataSourceName, DatabaseDataSourceOptions options)
|
|
{
|
|
var results = new List<ValidationResult>();
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据读写角色选择连接串,从读连接随机分配。
|
|
/// </summary>
|
|
/// <param name="options">数据源配置。</param>
|
|
/// <param name="role">连接角色。</param>
|
|
/// <returns>可用连接串。</returns>
|
|
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);
|
|
}
|
|
}
|