using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using FoodLabeling.Th.Application.Contracts.Options; using SqlSugar; namespace FoodLabeling.Th.Application.MultiTenancy; /// /// 根据配置生成 MySQL 租户库连接串 /// public static class TenantDatabaseConnectionStringBuilder { private const int FallbackHashLength = 8; private static readonly Regex SafeNameRegex = new(@"[^a-z0-9_]", RegexOptions.Compiled); /// /// 将租户名或显式 DatabaseKey 规范化为 MySQL 库名片段(小写字母、数字、下划线)。 /// 纯中文等非 ASCII 名称在清洗为空时,使用基于名称的稳定 hash 兜底(t + 8 位十六进制)。 /// public static string NormalizeTenantDatabaseKey(string tenantName, string? explicitDatabaseKey = null) { if (!string.IsNullOrWhiteSpace(explicitDatabaseKey)) { return NormalizeSanitizedKey(explicitDatabaseKey.Trim(), "DatabaseKey 无效,无法生成数据库名"); } var key = SanitizeToDatabaseKey(tenantName); if (string.IsNullOrEmpty(key)) { key = BuildStableFallbackKey(tenantName); } return key; } public static string BuildDatabaseName( FoodLabelingThTenantDatabaseOptions options, string tenantName, string? databaseKey = null) { var key = NormalizeTenantDatabaseKey(tenantName, databaseKey); return options.DatabaseNameTemplate.Replace("{tenant}", key, StringComparison.OrdinalIgnoreCase); } public static string BuildMySqlConnectionString( FoodLabelingThTenantDatabaseOptions options, string tenantName, string? databaseKey = null) { var database = BuildDatabaseName(options, tenantName, databaseKey); return $"server={options.Server};port={options.Port};database={database};uid={options.UserId};pwd={options.Password};CharSet={options.CharSet};"; } public static DbType ResolveDbType(string? configuredDbType) { return Enum.TryParse(configuredDbType, true, out var dbType) ? dbType : DbType.MySql; } private static string SanitizeToDatabaseKey(string value) { var key = value.Trim().ToLowerInvariant(); key = SafeNameRegex.Replace(key, "_"); return Regex.Replace(key, "_+", "_").Trim('_'); } private static string NormalizeSanitizedKey(string value, string invalidMessage) { var key = SanitizeToDatabaseKey(value); if (string.IsNullOrEmpty(key)) { throw new ArgumentException(invalidMessage); } return key; } /// /// 基于租户名称生成稳定、可重复的短 key,避免纯非 ASCII 名称清洗后为空。 /// private static string BuildStableFallbackKey(string tenantName) { var hashBytes = SHA256.HashData(Encoding.UTF8.GetBytes(tenantName.Trim())); var hex = Convert.ToHexString(hashBytes).ToLowerInvariant(); return "t" + hex[..FallbackHashLength]; } }