using Volo.Abp; using Volo.Abp.MultiTenancy; using Yi.Framework.SqlSugarCore.Abstractions; namespace FoodLabeling.Application.Helpers; /// /// 独立库多租户下,业务 API 须已解析到租户上下文 /// public static class TenantContextGuard { /// /// 平台主库登录(JWT/__tenant 均无业务租户)时访问 fl_* 等业务表的友好提示。 /// public const string PlatformCannotAccessBusinessDataMessage = "当前为平台主库登录,无法访问公司业务数据(标签/产品/成员等)。请选择具体公司登录,或使用平台「公司管理」相关接口。"; public static void EnsureTenantResolved(ICurrentTenant currentTenant, string? operation = null) { if (currentTenant.Id.HasValue && currentTenant.Id.Value != Guid.Empty) { return; } var hint = string.IsNullOrWhiteSpace(operation) ? "未识别租户上下文" : $"{operation}:未识别租户上下文"; throw new UserFriendlyException( $"{hint}。请使用泰额登录接口(th-web-auth / th-app-auth)选择具体公司登录,或请求头 __tenant 携带租户 Id。"); } /// /// 泰额 SaaS 多租户开启时,业务表(fl_* / location 等)必须走租户库,禁止落到 host。 /// public static void EnsureBusinessTenantIfSaas( ICurrentTenant currentTenant, DbConnOptions? dbConnOptions, string? operation = null) { if (dbConnOptions is null || !dbConnOptions.EnabledSaasMultiTenancy) { return; } EnsureTenantResolved(currentTenant, operation); } /// /// 判断当前 DbContext 是否连到平台主库(antis-foodlabeling-host)。 /// public static bool IsConnectedToHostDatabase(ISqlSugarDbContext dbContext, DbConnOptions dbConnOptions) { var dbName = dbContext.SqlSugarClient.Ado.Connection.Database; var hostDbName = TryExtractDatabaseName(dbConnOptions.Url); return !string.IsNullOrWhiteSpace(hostDbName) && string.Equals(dbName, hostDbName, StringComparison.OrdinalIgnoreCase); } /// /// SaaS 模式下校验 DbContext 未落到 host 主库(双保险,避免缺表 500)。 /// public static void EnsureNotHostDatabaseIfSaas( ISqlSugarDbContext dbContext, DbConnOptions dbConnOptions, string? operation = null) { if (!dbConnOptions.EnabledSaasMultiTenancy) { return; } if (!IsConnectedToHostDatabase(dbContext, dbConnOptions)) { return; } var hint = string.IsNullOrWhiteSpace(operation) ? PlatformCannotAccessBusinessDataMessage : $"{operation}:{PlatformCannotAccessBusinessDataMessage}"; throw new UserFriendlyException(hint); } private static string? TryExtractDatabaseName(string? connectionString) { if (string.IsNullOrWhiteSpace(connectionString)) { return null; } foreach (var part in connectionString.Split(';', StringSplitOptions.RemoveEmptyEntries)) { var kv = part.Split('=', 2, StringSplitOptions.TrimEntries); if (kv.Length == 2 && (kv[0].Equals("database", StringComparison.OrdinalIgnoreCase) || kv[0].Equals("Database", StringComparison.OrdinalIgnoreCase))) { return kv[1].Trim(); } } return null; } }