refactor: 禁止 TenantId=0 并改为系统租户

This commit is contained in:
root
2026-01-29 13:16:15 +00:00
parent 77836e270f
commit bc1c4cc41b
4 changed files with 48 additions and 14 deletions

View File

@@ -327,7 +327,7 @@ public sealed class DictionaryAppService(
var tenantId = tenantProvider.GetCurrentTenantId();
if (scope == DictionaryScope.System)
{
EnsurePlatformTenant(tenantId);
EnsureSystemTenant(tenantId);
return 0;
}
@@ -362,9 +362,9 @@ public sealed class DictionaryAppService(
}
}
private void EnsurePlatformTenant(long tenantId)
private void EnsureSystemTenant(long tenantId)
{
// 1. (空行后) 系统字典只能在平台租户TenantId=0上下文中操作
// 1. (空行后) 系统字典只能在系统租户TenantId=0上下文中操作
if (tenantId != 0)
{
throw new BusinessException(ErrorCodes.Forbidden, "租户端不允许操作系统字典");

View File

@@ -5,7 +5,7 @@ using TakeoutSaaS.Shared.Abstractions.Entities;
namespace TakeoutSaaS.Domain.Tenants.Entities;
/// <summary>
/// 平台租户信息,描述租户的生命周期与基础资料。
/// 租户信息,描述租户的生命周期与基础资料。
/// </summary>
public sealed class Tenant : AuditableEntityBase
{

View File

@@ -39,7 +39,7 @@ public sealed class AppDataSeeder(
var appDbContext = scope.ServiceProvider.GetRequiredService<TakeoutAppDbContext>();
var dictionaryDbContext = scope.ServiceProvider.GetRequiredService<DictionaryDbContext>();
await EnsurePlatformTenantAsync(appDbContext, cancellationToken);
await EnsureSystemTenantAsync(appDbContext, cancellationToken);
var defaultTenantId = await EnsureDefaultTenantAsync(appDbContext, cancellationToken);
await EnsureDictionarySeedsAsync(dictionaryDbContext, defaultTenantId, cancellationToken);
@@ -132,9 +132,9 @@ public sealed class AppDataSeeder(
}
/// <summary>
/// 确保平台租户存在。
/// 确保系统租户存在TenantId=0用于系统级数据归属
/// </summary>
private async Task EnsurePlatformTenantAsync(TakeoutAppDbContext dbContext, CancellationToken cancellationToken)
private async Task EnsureSystemTenantAsync(TakeoutAppDbContext dbContext, CancellationToken cancellationToken)
{
var existingTenant = await dbContext.Tenants
.IgnoreQueryFilters()
@@ -142,20 +142,47 @@ public sealed class AppDataSeeder(
if (existingTenant != null)
{
// 1. (空行后) 若历史数据仍为 PLATFORM则自动修正为 SYSTEM
var updated = false;
if (!string.Equals(existingTenant.Code, "SYSTEM", StringComparison.Ordinal))
{
existingTenant.Code = "SYSTEM";
updated = true;
}
if (!string.Equals(existingTenant.Name, "System", StringComparison.Ordinal))
{
existingTenant.Name = "System";
updated = true;
}
if (existingTenant.Status != TenantStatus.Active)
{
existingTenant.Status = TenantStatus.Active;
updated = true;
}
if (updated)
{
dbContext.Tenants.Update(existingTenant);
await dbContext.SaveChangesAsync(cancellationToken);
logger.LogInformation("AppSeed 已更新系统租户 SYSTEM");
}
return;
}
var tenant = new Tenant
{
Id = 0,
Code = "PLATFORM",
Name = "Platform",
Code = "SYSTEM",
Name = "System",
Status = TenantStatus.Active
};
await dbContext.Tenants.AddAsync(tenant, cancellationToken);
await dbContext.SaveChangesAsync(cancellationToken);
logger.LogInformation("AppSeed 已创建平台租户 PLATFORM");
logger.LogInformation("AppSeed 已创建系统租户 SYSTEM");
}
/// <summary>

View File

@@ -89,18 +89,25 @@ public sealed class TenantResolutionMiddleware(
private static TenantContext ResolveTenant(HttpContext context, TenantResolutionOptions options)
{
var request = context.Request;
var isAuthenticated = context.User?.Identity?.IsAuthenticated == true;
// 1. Token Claim已认证请求必须以 Claim 为准,避免 Header 覆盖导致跨租户访问)
var claim = context.User?.FindFirst("tenant_id");
if (claim != null && long.TryParse(claim.Value, out var claimTenant))
if (claim != null && long.TryParse(claim.Value, out var claimTenant) && claimTenant > 0)
{
return new TenantContext(claimTenant, null, "claim:tenant_id");
}
// 1.1 (空行后) 已认证但缺少合法租户 Claim则视为未解析不允许 Header 覆盖)
if (isAuthenticated)
{
return TenantContext.Empty;
}
// 2. Header 中的租户 ID
if (!string.IsNullOrWhiteSpace(options.TenantIdHeaderName) &&
request.Headers.TryGetValue(options.TenantIdHeaderName, out var tenantHeader) &&
long.TryParse(tenantHeader.FirstOrDefault(), out var headerTenantId))
long.TryParse(tenantHeader.FirstOrDefault(), out var headerTenantId) &&
headerTenantId > 0)
{
return new TenantContext(headerTenantId, null, $"header:{options.TenantIdHeaderName}");
}
@@ -120,7 +127,7 @@ public sealed class TenantResolutionMiddleware(
var host = request.Host.Host;
if (!string.IsNullOrWhiteSpace(host))
{
if (options.DomainTenantMap.TryGetValue(host, out var tenantFromHost))
if (options.DomainTenantMap.TryGetValue(host, out var tenantFromHost) && tenantFromHost > 0)
{
return new TenantContext(tenantFromHost, null, $"host:{host}");
}
@@ -143,7 +150,7 @@ public sealed class TenantResolutionMiddleware(
return false;
}
return options.CodeTenantMap.TryGetValue(code, out tenantId);
return options.CodeTenantMap.TryGetValue(code, out tenantId) && tenantId > 0;
}
private static string? ResolveCodeFromHost(string host, string? rootDomain)