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(); var tenantId = tenantProvider.GetCurrentTenantId();
if (scope == DictionaryScope.System) if (scope == DictionaryScope.System)
{ {
EnsurePlatformTenant(tenantId); EnsureSystemTenant(tenantId);
return 0; 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) if (tenantId != 0)
{ {
throw new BusinessException(ErrorCodes.Forbidden, "租户端不允许操作系统字典"); throw new BusinessException(ErrorCodes.Forbidden, "租户端不允许操作系统字典");

View File

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

View File

@@ -39,7 +39,7 @@ public sealed class AppDataSeeder(
var appDbContext = scope.ServiceProvider.GetRequiredService<TakeoutAppDbContext>(); var appDbContext = scope.ServiceProvider.GetRequiredService<TakeoutAppDbContext>();
var dictionaryDbContext = scope.ServiceProvider.GetRequiredService<DictionaryDbContext>(); var dictionaryDbContext = scope.ServiceProvider.GetRequiredService<DictionaryDbContext>();
await EnsurePlatformTenantAsync(appDbContext, cancellationToken); await EnsureSystemTenantAsync(appDbContext, cancellationToken);
var defaultTenantId = await EnsureDefaultTenantAsync(appDbContext, cancellationToken); var defaultTenantId = await EnsureDefaultTenantAsync(appDbContext, cancellationToken);
await EnsureDictionarySeedsAsync(dictionaryDbContext, defaultTenantId, cancellationToken); await EnsureDictionarySeedsAsync(dictionaryDbContext, defaultTenantId, cancellationToken);
@@ -132,9 +132,9 @@ public sealed class AppDataSeeder(
} }
/// <summary> /// <summary>
/// 确保平台租户存在。 /// 确保系统租户存在TenantId=0用于系统级数据归属
/// </summary> /// </summary>
private async Task EnsurePlatformTenantAsync(TakeoutAppDbContext dbContext, CancellationToken cancellationToken) private async Task EnsureSystemTenantAsync(TakeoutAppDbContext dbContext, CancellationToken cancellationToken)
{ {
var existingTenant = await dbContext.Tenants var existingTenant = await dbContext.Tenants
.IgnoreQueryFilters() .IgnoreQueryFilters()
@@ -142,20 +142,47 @@ public sealed class AppDataSeeder(
if (existingTenant != null) 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; return;
} }
var tenant = new Tenant var tenant = new Tenant
{ {
Id = 0, Id = 0,
Code = "PLATFORM", Code = "SYSTEM",
Name = "Platform", Name = "System",
Status = TenantStatus.Active Status = TenantStatus.Active
}; };
await dbContext.Tenants.AddAsync(tenant, cancellationToken); await dbContext.Tenants.AddAsync(tenant, cancellationToken);
await dbContext.SaveChangesAsync(cancellationToken); await dbContext.SaveChangesAsync(cancellationToken);
logger.LogInformation("AppSeed 已创建平台租户 PLATFORM"); logger.LogInformation("AppSeed 已创建系统租户 SYSTEM");
} }
/// <summary> /// <summary>

View File

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