TenantContextGuard.cs 3.6 KB
using Volo.Abp;
using Volo.Abp.MultiTenancy;
using Yi.Framework.SqlSugarCore.Abstractions;

namespace FoodLabeling.Application.Helpers;

/// <summary>
/// 独立库多租户下,业务 API 须已解析到租户上下文
/// </summary>
public static class TenantContextGuard
{
    /// <summary>
    /// 平台主库登录(JWT/__tenant 均无业务租户)时访问 fl_* 等业务表的友好提示。
    /// </summary>
    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。");
    }

    /// <summary>
    /// 泰额 SaaS 多租户开启时,业务表(fl_* / location 等)必须走租户库,禁止落到 host。
    /// </summary>
    public static void EnsureBusinessTenantIfSaas(
        ICurrentTenant currentTenant,
        DbConnOptions? dbConnOptions,
        string? operation = null)
    {
        if (dbConnOptions is null || !dbConnOptions.EnabledSaasMultiTenancy)
        {
            return;
        }

        EnsureTenantResolved(currentTenant, operation);
    }

    /// <summary>
    /// 判断当前 DbContext 是否连到平台主库(antis-foodlabeling-host)。
    /// </summary>
    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);
    }

    /// <summary>
    /// SaaS 模式下校验 DbContext 未落到 host 主库(双保险,避免缺表 500)。
    /// </summary>
    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;
    }
}