refactor: 引入管理端 DbContext 禁用租户过滤

This commit is contained in:
2026-01-29 03:52:11 +00:00
parent f7e21db88a
commit 65c4c00b87
29 changed files with 85 additions and 38 deletions

View File

@@ -37,7 +37,7 @@ public static class AppServiceCollectionExtensions
public static IServiceCollection AddAppInfrastructure(this IServiceCollection services, IConfiguration configuration)
{
services.AddDatabaseInfrastructure(configuration);
services.AddPostgresDbContext<TakeoutAppDbContext>(DatabaseConstants.AppDataSource);
services.AddPostgresDbContext<TakeoutAdminDbContext>(DatabaseConstants.AppDataSource);
services.AddPostgresDbContext<TakeoutLogsDbContext>(DatabaseConstants.LogsDataSource);
services.AddScoped<IMerchantRepository, EfMerchantRepository>();

View File

@@ -36,7 +36,7 @@ public sealed class AppDataSeeder(
}
using var scope = serviceProvider.CreateScope();
var appDbContext = scope.ServiceProvider.GetRequiredService<TakeoutAppDbContext>();
var appDbContext = scope.ServiceProvider.GetRequiredService<TakeoutAdminDbContext>();
var dictionaryDbContext = scope.ServiceProvider.GetRequiredService<DictionaryDbContext>();
await EnsurePlatformTenantAsync(appDbContext, cancellationToken);
@@ -52,7 +52,7 @@ public sealed class AppDataSeeder(
/// <summary>
/// 确保默认租户存在。
/// </summary>
private async Task<long?> EnsureDefaultTenantAsync(TakeoutAppDbContext dbContext, CancellationToken cancellationToken)
private async Task<long?> EnsureDefaultTenantAsync(TakeoutAdminDbContext dbContext, CancellationToken cancellationToken)
{
var tenantOptions = _options.DefaultTenant;
if (tenantOptions == null || string.IsNullOrWhiteSpace(tenantOptions.Code) || string.IsNullOrWhiteSpace(tenantOptions.Name))
@@ -134,7 +134,7 @@ public sealed class AppDataSeeder(
/// <summary>
/// 确保平台租户存在。
/// </summary>
private async Task EnsurePlatformTenantAsync(TakeoutAppDbContext dbContext, CancellationToken cancellationToken)
private async Task EnsurePlatformTenantAsync(TakeoutAdminDbContext dbContext, CancellationToken cancellationToken)
{
var existingTenant = await dbContext.Tenants
.IgnoreQueryFilters()

View File

@@ -9,7 +9,7 @@ namespace TakeoutSaaS.Infrastructure.App.Persistence.Repositories;
/// <summary>
/// 租户账单仓储实现EF Core
/// </summary>
public sealed class TenantBillingRepository(TakeoutAppDbContext context) : ITenantBillingRepository
public sealed class TenantBillingRepository(TakeoutAdminDbContext context) : ITenantBillingRepository
{
/// <inheritdoc />
public async Task<IReadOnlyList<TenantBillingStatement>> SearchAsync(

View File

@@ -9,7 +9,7 @@ namespace TakeoutSaaS.Infrastructure.App.Persistence.Repositories;
/// <summary>
/// 租户支付记录仓储实现EF Core
/// </summary>
public sealed class TenantPaymentRepository(TakeoutAppDbContext context) : ITenantPaymentRepository
public sealed class TenantPaymentRepository(TakeoutAdminDbContext context) : ITenantPaymentRepository
{
/// <inheritdoc />
public async Task<IReadOnlyList<TenantPayment>> GetByBillingIdAsync(long billingStatementId, CancellationToken cancellationToken = default)

View File

@@ -0,0 +1,30 @@
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using TakeoutSaaS.Shared.Abstractions.Ids;
using TakeoutSaaS.Shared.Abstractions.Security;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Infrastructure.App.Persistence;
/// <summary>
/// 管理端业务主库 DbContext不启用租户全局过滤用于跨租户查询与平台级任务。
/// </summary>
public sealed class TakeoutAdminDbContext(
DbContextOptions<TakeoutAdminDbContext> options,
ITenantProvider tenantProvider,
ICurrentUserAccessor? currentUserAccessor = null,
IIdGenerator? idGenerator = null,
IHttpContextAccessor? httpContextAccessor = null)
: TakeoutAppDbContext(options, tenantProvider, currentUserAccessor, idGenerator, httpContextAccessor)
{
/// <summary>
/// 配置实体映射关系(不启用租户过滤)。
/// </summary>
/// <param name="modelBuilder">模型构建器。</param>
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// 1. 构建基础模型(复用实体映射)
OnModelCreatingCore(modelBuilder);
}
}

View File

@@ -33,8 +33,8 @@ namespace TakeoutSaaS.Infrastructure.App.Persistence;
/// <summary>
/// 业务主库 DbContext。
/// </summary>
public sealed class TakeoutAppDbContext(
DbContextOptions<TakeoutAppDbContext> options,
public class TakeoutAppDbContext(
DbContextOptions options,
ITenantProvider tenantProvider,
ICurrentUserAccessor? currentUserAccessor = null,
IIdGenerator? idGenerator = null,
@@ -382,10 +382,29 @@ public sealed class TakeoutAppDbContext(
/// </summary>
/// <param name="modelBuilder">模型构建器。</param>
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// 1. 构建基础模型(软删除/注释 + 实体映射)
OnModelCreatingCore(modelBuilder);
// 2. (空行后) 应用租户过滤
ApplyTenantQueryFilters(modelBuilder);
}
/// <summary>
/// 共享模型构建逻辑:软删除/注释 + 全部实体映射(不包含租户过滤)。
/// </summary>
/// <param name="modelBuilder">模型构建器。</param>
protected void OnModelCreatingCore(ModelBuilder modelBuilder)
{
// 1. 调用基类配置
base.OnModelCreating(modelBuilder);
// 2. 配置全部实体映射
// 2. (空行后) 配置全部实体映射
ConfigureModel(modelBuilder);
}
internal static void ConfigureModel(ModelBuilder modelBuilder)
{
ConfigureTenant(modelBuilder.Entity<Tenant>());
ConfigureMerchant(modelBuilder.Entity<Merchant>());
ConfigureStore(modelBuilder.Entity<Store>());
@@ -470,8 +489,6 @@ public sealed class TakeoutAppDbContext(
ConfigureMetricDefinition(modelBuilder.Entity<MetricDefinition>());
ConfigureMetricSnapshot(modelBuilder.Entity<MetricSnapshot>());
ConfigureMetricAlertRule(modelBuilder.Entity<MetricAlertRule>());
ApplyTenantQueryFilters(modelBuilder);
}
private static void ConfigureTenant(EntityTypeBuilder<Tenant> builder)

View File

@@ -12,7 +12,7 @@ namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <remarks>
/// 初始化仓储。
/// </remarks>
public sealed class EfDeliveryRepository(TakeoutAppDbContext context) : IDeliveryRepository
public sealed class EfDeliveryRepository(TakeoutAdminDbContext context) : IDeliveryRepository
{
/// <inheritdoc />
public Task<DeliveryOrder?> FindByIdAsync(long deliveryOrderId, long tenantId, CancellationToken cancellationToken = default)

View File

@@ -12,7 +12,7 @@ namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <remarks>
/// 提供库存与批次的读写能力。
/// </remarks>
public sealed class EfInventoryRepository(TakeoutAppDbContext context) : IInventoryRepository
public sealed class EfInventoryRepository(TakeoutAdminDbContext context) : IInventoryRepository
{
/// <inheritdoc />
public Task<InventoryItem?> FindByIdAsync(long inventoryItemId, long tenantId, CancellationToken cancellationToken = default)

View File

@@ -8,7 +8,7 @@ namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <summary>
/// 商户类目的 EF Core 仓储实现。
/// </summary>
public sealed class EfMerchantCategoryRepository(TakeoutAppDbContext context)
public sealed class EfMerchantCategoryRepository(TakeoutAdminDbContext context)
: IMerchantCategoryRepository
{
/// <inheritdoc />

View File

@@ -14,7 +14,7 @@ namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <remarks>
/// 初始化仓储。
/// </remarks>
public sealed class EfMerchantRepository(TakeoutAppDbContext context, TakeoutLogsDbContext logsContext) : IMerchantRepository
public sealed class EfMerchantRepository(TakeoutAdminDbContext context, TakeoutLogsDbContext logsContext) : IMerchantRepository
{
/// <inheritdoc />
public Task<Merchant?> FindByIdAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default)

View File

@@ -13,7 +13,7 @@ namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <remarks>
/// 初始化仓储。
/// </remarks>
public sealed class EfOrderRepository(TakeoutAppDbContext context) : IOrderRepository
public sealed class EfOrderRepository(TakeoutAdminDbContext context) : IOrderRepository
{
/// <inheritdoc />
public Task<Order?> FindByIdAsync(long orderId, long tenantId, CancellationToken cancellationToken = default)

View File

@@ -12,7 +12,7 @@ namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <remarks>
/// 初始化仓储。
/// </remarks>
public sealed class EfPaymentRepository(TakeoutAppDbContext context) : IPaymentRepository
public sealed class EfPaymentRepository(TakeoutAdminDbContext context) : IPaymentRepository
{
/// <inheritdoc />
public Task<PaymentRecord?> FindByIdAsync(long paymentId, long tenantId, CancellationToken cancellationToken = default)

View File

@@ -14,7 +14,7 @@ namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <remarks>
/// 初始化仓储。
/// </remarks>
public sealed class EfProductRepository(TakeoutAppDbContext context) : IProductRepository
public sealed class EfProductRepository(TakeoutAdminDbContext context) : IProductRepository
{
/// <inheritdoc />
public Task<Product?> FindByIdAsync(long productId, long tenantId, CancellationToken cancellationToken = default)

View File

@@ -9,7 +9,7 @@ namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <summary>
/// EF 配额包仓储实现。
/// </summary>
public sealed class EfQuotaPackageRepository(TakeoutAppDbContext context) : IQuotaPackageRepository
public sealed class EfQuotaPackageRepository(TakeoutAdminDbContext context) : IQuotaPackageRepository
{
#region

View File

@@ -9,7 +9,7 @@ namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <summary>
/// 统计数据仓储实现。
/// </summary>
public sealed class EfStatisticsRepository(TakeoutAppDbContext dbContext) : IStatisticsRepository
public sealed class EfStatisticsRepository(TakeoutAdminDbContext dbContext) : IStatisticsRepository
{
#region

View File

@@ -14,7 +14,7 @@ namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <remarks>
/// 初始化仓储。
/// </remarks>
public sealed class EfStoreRepository(TakeoutAppDbContext context) : IStoreRepository
public sealed class EfStoreRepository(TakeoutAdminDbContext context) : IStoreRepository
{
/// <inheritdoc />
public Task<Store?> FindByIdAsync(long storeId, long tenantId, CancellationToken cancellationToken = default)

View File

@@ -10,7 +10,7 @@ namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <summary>
/// 订阅管理仓储实现。
/// </summary>
public sealed class EfSubscriptionRepository(TakeoutAppDbContext dbContext, TakeoutLogsDbContext logsContext) : ISubscriptionRepository
public sealed class EfSubscriptionRepository(TakeoutAdminDbContext dbContext, TakeoutLogsDbContext logsContext) : ISubscriptionRepository
{
#region

View File

@@ -8,7 +8,7 @@ namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <summary>
/// EF 公告已读仓储。
/// </summary>
public sealed class EfTenantAnnouncementReadRepository(TakeoutAppDbContext context) : ITenantAnnouncementReadRepository
public sealed class EfTenantAnnouncementReadRepository(TakeoutAdminDbContext context) : ITenantAnnouncementReadRepository
{
/// <inheritdoc />
public Task<IReadOnlyList<TenantAnnouncementRead>> GetByAnnouncementAsync(long tenantId, long announcementId, CancellationToken cancellationToken = default)

View File

@@ -9,7 +9,7 @@ namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <summary>
/// EF 租户公告仓储。
/// </summary>
public sealed class EfTenantAnnouncementRepository(TakeoutAppDbContext context) : ITenantAnnouncementRepository
public sealed class EfTenantAnnouncementRepository(TakeoutAdminDbContext context) : ITenantAnnouncementRepository
{
/// <inheritdoc />
public async Task<IReadOnlyList<TenantAnnouncement>> SearchAsync(

View File

@@ -9,7 +9,7 @@ namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <summary>
/// EF 租户通知仓储。
/// </summary>
public sealed class EfTenantNotificationRepository(TakeoutAppDbContext context) : ITenantNotificationRepository
public sealed class EfTenantNotificationRepository(TakeoutAdminDbContext context) : ITenantNotificationRepository
{
/// <inheritdoc />
public Task<IReadOnlyList<TenantNotification>> SearchAsync(

View File

@@ -9,7 +9,7 @@ namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <summary>
/// 租户套餐仓储实现。
/// </summary>
public sealed class EfTenantPackageRepository(TakeoutAppDbContext context) : ITenantPackageRepository
public sealed class EfTenantPackageRepository(TakeoutAdminDbContext context) : ITenantPackageRepository
{
/// <inheritdoc />
public Task<TenantPackage?> FindByIdAsync(long id, CancellationToken cancellationToken = default)

View File

@@ -7,7 +7,7 @@ namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <summary>
/// 租户配额使用历史仓储实现。
/// </summary>
public sealed class EfTenantQuotaUsageHistoryRepository(TakeoutAppDbContext context) : ITenantQuotaUsageHistoryRepository
public sealed class EfTenantQuotaUsageHistoryRepository(TakeoutAdminDbContext context) : ITenantQuotaUsageHistoryRepository
{
/// <inheritdoc />
public Task AddAsync(TenantQuotaUsageHistory history, CancellationToken cancellationToken = default)

View File

@@ -9,7 +9,7 @@ namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <summary>
/// 租户配额使用仓储实现。
/// </summary>
public sealed class EfTenantQuotaUsageRepository(TakeoutAppDbContext context) : ITenantQuotaUsageRepository
public sealed class EfTenantQuotaUsageRepository(TakeoutAdminDbContext context) : ITenantQuotaUsageRepository
{
/// <inheritdoc />
public Task<TenantQuotaUsage?> FindAsync(long tenantId, TenantQuotaType quotaType, CancellationToken cancellationToken = default)

View File

@@ -11,7 +11,7 @@ namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <summary>
/// 租户聚合的 EF Core 仓储实现。
/// </summary>
public sealed class EfTenantRepository(TakeoutAppDbContext context, TakeoutLogsDbContext logsContext) : ITenantRepository
public sealed class EfTenantRepository(TakeoutAdminDbContext context, TakeoutLogsDbContext logsContext) : ITenantRepository
{
/// <inheritdoc />
public Task<Tenant?> FindByIdAsync(long tenantId, CancellationToken cancellationToken = default)

View File

@@ -11,7 +11,7 @@ namespace TakeoutSaaS.Infrastructure.App.Services;
/// 门店定时任务服务实现。
/// </summary>
public sealed class StoreSchedulerService(
TakeoutAppDbContext context,
TakeoutAdminDbContext context,
ILogger<StoreSchedulerService> logger)
: IStoreSchedulerService
{

View File

@@ -68,7 +68,7 @@ public sealed class AutoRenewalService : BackgroundService
_logger.LogInformation("开始处理自动续费");
using var scope = _serviceProvider.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<TakeoutAppDbContext>();
var dbContext = scope.ServiceProvider.GetRequiredService<TakeoutAdminDbContext>();
var idGenerator = scope.ServiceProvider.GetRequiredService<IIdGenerator>();
var now = DateTime.UtcNow;

View File

@@ -68,7 +68,7 @@ public sealed class RenewalReminderService : BackgroundService
_logger.LogInformation("开始发送续费提醒");
using var scope = _serviceProvider.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<TakeoutAppDbContext>();
var dbContext = scope.ServiceProvider.GetRequiredService<TakeoutAdminDbContext>();
var idGenerator = scope.ServiceProvider.GetRequiredService<IIdGenerator>();
var now = DateTime.UtcNow;

View File

@@ -66,7 +66,7 @@ public sealed class SubscriptionExpiryCheckService : BackgroundService
_logger.LogInformation("开始执行订阅到期检查");
using var scope = _serviceProvider.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<TakeoutAppDbContext>();
var dbContext = scope.ServiceProvider.GetRequiredService<TakeoutAdminDbContext>();
var now = DateTime.UtcNow;
var gracePeriodDays = _options.GracePeriodDays;

View File

@@ -13,18 +13,18 @@ public sealed class SqliteTestDatabase : IDisposable
{
_connection = new SqliteConnection("Filename=:memory:");
_connection.Open();
Options = new DbContextOptionsBuilder<TakeoutAppDbContext>()
Options = new DbContextOptionsBuilder<TakeoutAdminDbContext>()
.UseSqlite(_connection)
.EnableSensitiveDataLogging()
.Options;
}
public DbContextOptions<TakeoutAppDbContext> Options { get; }
public DbContextOptions<TakeoutAdminDbContext> Options { get; }
public TakeoutAppDbContext CreateContext(long tenantId, long userId = 0)
public TakeoutAdminDbContext CreateContext(long tenantId, long userId = 0)
{
EnsureCreated();
return new TakeoutAppDbContext(Options, new TestTenantProvider(tenantId), new TestCurrentUserAccessor(userId));
return new TakeoutAdminDbContext(Options, new TestTenantProvider(tenantId), new TestCurrentUserAccessor(userId));
}
public void EnsureCreated()
@@ -34,7 +34,7 @@ public sealed class SqliteTestDatabase : IDisposable
return;
}
using var context = new TakeoutAppDbContext(Options, new TestTenantProvider(1));
using var context = new TakeoutAdminDbContext(Options, new TestTenantProvider(1));
context.Database.EnsureCreated();
_initialized = true;
}