feat: 实现动态租户解析与移除菜单回退逻辑
1. 新增 ITenantCodeResolver 接口和 DatabaseTenantCodeResolver 实现 2. 修改 TenantResolutionMiddleware 支持从数据库动态解析租户编码 3. ITenantRepository 新增 FindIdByCodeAsync 方法 4. 移除 EfMenuRepository 中危险的系统菜单回退逻辑 5. 调整服务注册顺序确保依赖正确注入 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -162,6 +162,20 @@ public sealed class EfTenantRepository(TakeoutAppDbContext context, TakeoutLogsD
|
||||
return context.Tenants.AnyAsync(x => x.Code == normalized, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<long?> FindIdByCodeAsync(string code, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 标准化编码
|
||||
var normalized = code.Trim();
|
||||
|
||||
// 2. 查询租户 ID(仅查询未删除且状态正常的租户)
|
||||
return context.Tenants
|
||||
.AsNoTracking()
|
||||
.Where(x => x.Code == normalized && x.DeletedAt == null)
|
||||
.Select(x => (long?)x.Id)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<bool> ExistsByNameAsync(string name, long? excludeTenantId = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
||||
@@ -2,65 +2,37 @@ using Microsoft.EntityFrameworkCore;
|
||||
using TakeoutSaaS.Domain.Identity.Entities;
|
||||
using TakeoutSaaS.Domain.Identity.Repositories;
|
||||
using TakeoutSaaS.Infrastructure.Identity.Persistence;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Infrastructure.Identity.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// 菜单仓储 EF 实现。
|
||||
/// </summary>
|
||||
public sealed class EfMenuRepository(IdentityDbContext dbContext, ITenantContextAccessor tenantContextAccessor) : IMenuRepository
|
||||
public sealed class EfMenuRepository(IdentityDbContext dbContext) : IMenuRepository
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<MenuDefinition>> GetByTenantAsync(long tenantId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 优先返回租户自定义菜单
|
||||
// 1. 仅返回该租户的菜单,无回退逻辑
|
||||
var tenantMenus = await dbContext.MenuDefinitions
|
||||
.AsNoTracking()
|
||||
.Where(x => x.TenantId == tenantId)
|
||||
.Where(x => x.TenantId == tenantId && x.DeletedAt == null)
|
||||
.OrderBy(x => x.ParentId)
|
||||
.ThenBy(x => x.SortOrder)
|
||||
.ToListAsync(cancellationToken);
|
||||
if (tenantMenus.Count > 0)
|
||||
{
|
||||
return tenantMenus;
|
||||
}
|
||||
|
||||
// 2. (空行后) 回退系统默认菜单(TenantId=0)
|
||||
using (tenantContextAccessor.EnterTenantScope(0, "menu"))
|
||||
{
|
||||
var systemMenus = await dbContext.MenuDefinitions
|
||||
.AsNoTracking()
|
||||
.Where(x => x.TenantId == 0 && x.DeletedAt == null)
|
||||
.OrderBy(x => x.ParentId)
|
||||
.ThenBy(x => x.SortOrder)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return systemMenus;
|
||||
}
|
||||
return tenantMenus;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<MenuDefinition?> FindByIdAsync(long id, long tenantId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 优先查租户菜单
|
||||
var tenantMenu = await dbContext.MenuDefinitions
|
||||
// 1. 仅查询该租户的菜单,无回退逻辑
|
||||
return await dbContext.MenuDefinitions
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(
|
||||
x => x.Id == id && x.TenantId == tenantId,
|
||||
x => x.Id == id && x.TenantId == tenantId && x.DeletedAt == null,
|
||||
cancellationToken);
|
||||
if (tenantMenu != null)
|
||||
{
|
||||
return tenantMenu;
|
||||
}
|
||||
|
||||
// 2. (空行后) 回退查系统默认菜单(TenantId=0)
|
||||
using (tenantContextAccessor.EnterTenantScope(0, "menu"))
|
||||
{
|
||||
return await dbContext.MenuDefinitions
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(x => x.Id == id && x.TenantId == 0 && x.DeletedAt == null, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
Reference in New Issue
Block a user