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:
MSuMshk
2026-02-03 13:05:52 +08:00
parent cfacbf8363
commit e88c41c11e
10 changed files with 103 additions and 49 deletions

View File

@@ -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 />