refactor: 管理端去租户过滤并Portal化RBAC菜单

This commit is contained in:
2026-01-29 10:46:49 +00:00
parent ea9c20d8a9
commit b3639ff34b
115 changed files with 1106 additions and 1092 deletions

View File

@@ -1,5 +1,4 @@
using MediatR;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.Text.Json;
using TakeoutSaaS.Application.App.Subscriptions.Commands;
@@ -15,7 +14,6 @@ namespace TakeoutSaaS.Application.App.Subscriptions.Handlers;
/// </summary>
public sealed class BatchExtendSubscriptionsCommandHandler(
ISubscriptionRepository subscriptionRepository,
IHttpContextAccessor httpContextAccessor,
IIdGenerator idGenerator,
ILogger<BatchExtendSubscriptionsCommandHandler> logger)
: IRequestHandler<BatchExtendSubscriptionsCommand, BatchExtendResult>
@@ -23,8 +21,6 @@ public sealed class BatchExtendSubscriptionsCommandHandler(
/// <inheritdoc />
public async Task<BatchExtendResult> Handle(BatchExtendSubscriptionsCommand request, CancellationToken cancellationToken)
{
var ignoreTenantFilter = SubscriptionTenantAccess.ShouldIgnoreTenantFilter(httpContextAccessor);
var successCount = 0;
var failures = new List<BatchFailureItem>();
@@ -41,8 +37,7 @@ public sealed class BatchExtendSubscriptionsCommandHandler(
// 查询所有订阅
var subscriptions = await subscriptionRepository.FindByIdsAsync(
request.SubscriptionIds,
cancellationToken,
ignoreTenantFilter: ignoreTenantFilter);
cancellationToken);
foreach (var subscriptionId in request.SubscriptionIds)
{

View File

@@ -1,5 +1,4 @@
using MediatR;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.Text.Json;
using TakeoutSaaS.Application.App.Subscriptions.Commands;
@@ -15,7 +14,6 @@ namespace TakeoutSaaS.Application.App.Subscriptions.Handlers;
/// </summary>
public sealed class BatchSendReminderCommandHandler(
ISubscriptionRepository subscriptionRepository,
IHttpContextAccessor httpContextAccessor,
IIdGenerator idGenerator,
ILogger<BatchSendReminderCommandHandler> logger)
: IRequestHandler<BatchSendReminderCommand, BatchSendReminderResult>
@@ -23,16 +21,13 @@ public sealed class BatchSendReminderCommandHandler(
/// <inheritdoc />
public async Task<BatchSendReminderResult> Handle(BatchSendReminderCommand request, CancellationToken cancellationToken)
{
var ignoreTenantFilter = SubscriptionTenantAccess.ShouldIgnoreTenantFilter(httpContextAccessor);
var successCount = 0;
var failures = new List<BatchFailureItem>();
// 查询所有订阅及租户信息
var subscriptions = await subscriptionRepository.FindByIdsWithTenantAsync(
request.SubscriptionIds,
cancellationToken,
ignoreTenantFilter: ignoreTenantFilter);
cancellationToken);
foreach (var subscriptionId in request.SubscriptionIds)
{

View File

@@ -1,5 +1,4 @@
using MediatR;
using Microsoft.AspNetCore.Http;
using TakeoutSaaS.Application.App.Subscriptions.Commands;
using TakeoutSaaS.Application.App.Subscriptions.Dto;
using TakeoutSaaS.Application.App.Subscriptions.Queries;
@@ -15,7 +14,6 @@ namespace TakeoutSaaS.Application.App.Subscriptions.Handlers;
/// </summary>
public sealed class ChangeSubscriptionPlanCommandHandler(
ISubscriptionRepository subscriptionRepository,
IHttpContextAccessor httpContextAccessor,
IIdGenerator idGenerator,
IMediator mediator)
: IRequestHandler<ChangeSubscriptionPlanCommand, SubscriptionDetailDto?>
@@ -23,13 +21,10 @@ public sealed class ChangeSubscriptionPlanCommandHandler(
/// <inheritdoc />
public async Task<SubscriptionDetailDto?> Handle(ChangeSubscriptionPlanCommand request, CancellationToken cancellationToken)
{
var ignoreTenantFilter = SubscriptionTenantAccess.ShouldIgnoreTenantFilter(httpContextAccessor);
// 1. 查询订阅
var subscription = await subscriptionRepository.FindByIdAsync(
request.SubscriptionId,
cancellationToken,
ignoreTenantFilter: ignoreTenantFilter);
cancellationToken);
if (subscription == null)
{

View File

@@ -1,5 +1,4 @@
using MediatR;
using Microsoft.AspNetCore.Http;
using TakeoutSaaS.Application.App.Subscriptions.Commands;
using TakeoutSaaS.Application.App.Subscriptions.Dto;
using TakeoutSaaS.Application.App.Subscriptions.Queries;
@@ -15,7 +14,6 @@ namespace TakeoutSaaS.Application.App.Subscriptions.Handlers;
/// </summary>
public sealed class ExtendSubscriptionCommandHandler(
ISubscriptionRepository subscriptionRepository,
IHttpContextAccessor httpContextAccessor,
IIdGenerator idGenerator,
IMediator mediator)
: IRequestHandler<ExtendSubscriptionCommand, SubscriptionDetailDto?>
@@ -23,13 +21,10 @@ public sealed class ExtendSubscriptionCommandHandler(
/// <inheritdoc />
public async Task<SubscriptionDetailDto?> Handle(ExtendSubscriptionCommand request, CancellationToken cancellationToken)
{
var ignoreTenantFilter = SubscriptionTenantAccess.ShouldIgnoreTenantFilter(httpContextAccessor);
// 1. 查询订阅
var subscription = await subscriptionRepository.FindByIdAsync(
request.SubscriptionId,
cancellationToken,
ignoreTenantFilter: ignoreTenantFilter);
cancellationToken);
if (subscription == null)
{

View File

@@ -1,5 +1,4 @@
using MediatR;
using Microsoft.AspNetCore.Http;
using TakeoutSaaS.Application.App.Subscriptions.Dto;
using TakeoutSaaS.Application.App.Subscriptions.Queries;
using TakeoutSaaS.Application.App.Tenants;
@@ -12,20 +11,17 @@ namespace TakeoutSaaS.Application.App.Subscriptions.Handlers;
/// 订阅详情查询处理器。
/// </summary>
public sealed class GetSubscriptionDetailQueryHandler(
ISubscriptionRepository subscriptionRepository,
IHttpContextAccessor httpContextAccessor)
ISubscriptionRepository subscriptionRepository)
: IRequestHandler<GetSubscriptionDetailQuery, SubscriptionDetailDto?>
{
/// <inheritdoc />
public async Task<SubscriptionDetailDto?> Handle(GetSubscriptionDetailQuery request, CancellationToken cancellationToken)
{
var ignoreTenantFilter = SubscriptionTenantAccess.ShouldIgnoreTenantFilter(httpContextAccessor);
// 1. 查询订阅基础信息
var detail = await subscriptionRepository.GetDetailAsync(
request.SubscriptionId,
cancellationToken,
ignoreTenantFilter: ignoreTenantFilter);
includeDeleted: request.IncludeDeleted);
if (detail == null)
{
@@ -36,7 +32,7 @@ public sealed class GetSubscriptionDetailQueryHandler(
var quotaUsages = await subscriptionRepository.GetQuotaUsagesAsync(
detail.Subscription.TenantId,
cancellationToken,
ignoreTenantFilter: ignoreTenantFilter);
includeDeleted: request.IncludeDeleted);
var quotaUsageDtos = BuildQuotaUsageDtos(detail.Package, quotaUsages);
@@ -118,7 +114,7 @@ public sealed class GetSubscriptionDetailQueryHandler(
});
}
// Add any extra quota usages not covered by package fields (e.g. promotion slots).
// 补充套餐字段未覆盖的配额使用项(例如营销槽位等扩展配额)
foreach (var usage in usageByType.Values)
{
if (baselineTypes.Any(x => x.Type == usage.QuotaType))

View File

@@ -1,5 +1,4 @@
using MediatR;
using Microsoft.AspNetCore.Http;
using TakeoutSaaS.Application.App.Subscriptions.Dto;
using TakeoutSaaS.Application.App.Subscriptions.Queries;
using TakeoutSaaS.Domain.Tenants.Repositories;
@@ -11,15 +10,12 @@ namespace TakeoutSaaS.Application.App.Subscriptions.Handlers;
/// 订阅分页查询处理器。
/// </summary>
public sealed class GetSubscriptionListQueryHandler(
ISubscriptionRepository subscriptionRepository,
IHttpContextAccessor httpContextAccessor)
ISubscriptionRepository subscriptionRepository)
: IRequestHandler<GetSubscriptionListQuery, PagedResult<SubscriptionListDto>>
{
/// <inheritdoc />
public async Task<PagedResult<SubscriptionListDto>> Handle(GetSubscriptionListQuery request, CancellationToken cancellationToken)
{
var ignoreTenantFilter = SubscriptionTenantAccess.ShouldIgnoreTenantFilter(httpContextAccessor);
// 1. 构建查询过滤条件
var filter = new SubscriptionSearchFilter
{
@@ -37,7 +33,7 @@ public sealed class GetSubscriptionListQueryHandler(
var (items, total) = await subscriptionRepository.SearchPagedAsync(
filter,
cancellationToken,
ignoreTenantFilter: ignoreTenantFilter);
includeDeleted: request.IncludeDeleted);
// 3. 映射为 DTO
var dtos = items.Select(x => new SubscriptionListDto

View File

@@ -1,5 +1,4 @@
using MediatR;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.Text.Json;
using TakeoutSaaS.Application.App.Subscriptions.Commands;
@@ -14,7 +13,6 @@ namespace TakeoutSaaS.Application.App.Subscriptions.Handlers;
/// </summary>
public sealed class ProcessAutoRenewalCommandHandler(
ISubscriptionRepository subscriptionRepository,
IHttpContextAccessor httpContextAccessor,
ITenantBillingRepository billingRepository,
IIdGenerator idGenerator,
ILogger<ProcessAutoRenewalCommandHandler> logger)
@@ -23,8 +21,6 @@ public sealed class ProcessAutoRenewalCommandHandler(
/// <inheritdoc />
public async Task<ProcessAutoRenewalResult> Handle(ProcessAutoRenewalCommand request, CancellationToken cancellationToken)
{
var ignoreTenantFilter = SubscriptionTenantAccess.ShouldIgnoreTenantFilter(httpContextAccessor);
// 1. 计算续费阈值时间
var now = DateTime.UtcNow;
var renewalThreshold = now.AddDays(request.RenewalDaysBeforeExpiry);
@@ -33,8 +29,7 @@ public sealed class ProcessAutoRenewalCommandHandler(
var candidates = await subscriptionRepository.FindAutoRenewalCandidatesAsync(
now,
renewalThreshold,
cancellationToken,
ignoreTenantFilter: ignoreTenantFilter);
cancellationToken);
var createdBillCount = 0;
// 3. 遍历候选订阅,生成账单

View File

@@ -1,5 +1,4 @@
using MediatR;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.Text.Json;
using TakeoutSaaS.Application.App.Subscriptions.Commands;
@@ -15,7 +14,6 @@ namespace TakeoutSaaS.Application.App.Subscriptions.Handlers;
/// </summary>
public sealed class ProcessRenewalRemindersCommandHandler(
ISubscriptionRepository subscriptionRepository,
IHttpContextAccessor httpContextAccessor,
ITenantNotificationRepository notificationRepository,
IIdGenerator idGenerator,
ILogger<ProcessRenewalRemindersCommandHandler> logger)
@@ -26,8 +24,6 @@ public sealed class ProcessRenewalRemindersCommandHandler(
/// <inheritdoc />
public async Task<ProcessRenewalRemindersResult> Handle(ProcessRenewalRemindersCommand request, CancellationToken cancellationToken)
{
var ignoreTenantFilter = SubscriptionTenantAccess.ShouldIgnoreTenantFilter(httpContextAccessor);
// 1. 读取提醒配置
var now = DateTime.UtcNow;
var candidateCount = 0;
@@ -46,8 +42,7 @@ public sealed class ProcessRenewalRemindersCommandHandler(
var candidates = await subscriptionRepository.FindRenewalReminderCandidatesAsync(
startOfDay,
endOfDay,
cancellationToken,
ignoreTenantFilter: ignoreTenantFilter);
cancellationToken);
candidateCount += candidates.Count;
foreach (var item in candidates)

View File

@@ -1,5 +1,4 @@
using MediatR;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using TakeoutSaaS.Application.App.Subscriptions.Commands;
using TakeoutSaaS.Domain.Tenants.Enums;
@@ -12,26 +11,21 @@ namespace TakeoutSaaS.Application.App.Subscriptions.Handlers;
/// </summary>
public sealed class ProcessSubscriptionExpiryCommandHandler(
ISubscriptionRepository subscriptionRepository,
IHttpContextAccessor httpContextAccessor,
ILogger<ProcessSubscriptionExpiryCommandHandler> logger)
: IRequestHandler<ProcessSubscriptionExpiryCommand, ProcessSubscriptionExpiryResult>
{
/// <inheritdoc />
public async Task<ProcessSubscriptionExpiryResult> Handle(ProcessSubscriptionExpiryCommand request, CancellationToken cancellationToken)
{
var ignoreTenantFilter = SubscriptionTenantAccess.ShouldIgnoreTenantFilter(httpContextAccessor);
// 1. 查询到期订阅
var now = DateTime.UtcNow;
var expiredActive = await subscriptionRepository.FindExpiredActiveSubscriptionsAsync(
now,
cancellationToken,
ignoreTenantFilter: ignoreTenantFilter);
cancellationToken);
var gracePeriodExpired = await subscriptionRepository.FindGracePeriodExpiredSubscriptionsAsync(
now,
request.GracePeriodDays,
cancellationToken,
ignoreTenantFilter: ignoreTenantFilter);
cancellationToken);
// 2. 更新订阅状态
foreach (var subscription in expiredActive)

View File

@@ -1,5 +1,4 @@
using MediatR;
using Microsoft.AspNetCore.Http;
using TakeoutSaaS.Application.App.Subscriptions.Commands;
using TakeoutSaaS.Application.App.Subscriptions.Dto;
using TakeoutSaaS.Application.App.Subscriptions.Queries;
@@ -12,20 +11,16 @@ namespace TakeoutSaaS.Application.App.Subscriptions.Handlers;
/// </summary>
public sealed class UpdateSubscriptionCommandHandler(
ISubscriptionRepository subscriptionRepository,
IHttpContextAccessor httpContextAccessor,
IMediator mediator)
: IRequestHandler<UpdateSubscriptionCommand, SubscriptionDetailDto?>
{
/// <inheritdoc />
public async Task<SubscriptionDetailDto?> Handle(UpdateSubscriptionCommand request, CancellationToken cancellationToken)
{
var ignoreTenantFilter = SubscriptionTenantAccess.ShouldIgnoreTenantFilter(httpContextAccessor);
// 1. 查询订阅
var subscription = await subscriptionRepository.FindByIdAsync(
request.SubscriptionId,
cancellationToken,
ignoreTenantFilter: ignoreTenantFilter);
cancellationToken);
if (subscription == null)
{

View File

@@ -1,5 +1,4 @@
using MediatR;
using Microsoft.AspNetCore.Http;
using TakeoutSaaS.Application.App.Subscriptions.Commands;
using TakeoutSaaS.Application.App.Subscriptions.Dto;
using TakeoutSaaS.Application.App.Subscriptions.Queries;
@@ -12,20 +11,16 @@ namespace TakeoutSaaS.Application.App.Subscriptions.Handlers;
/// </summary>
public sealed class UpdateSubscriptionStatusCommandHandler(
ISubscriptionRepository subscriptionRepository,
IHttpContextAccessor httpContextAccessor,
IMediator mediator)
: IRequestHandler<UpdateSubscriptionStatusCommand, SubscriptionDetailDto?>
{
/// <inheritdoc />
public async Task<SubscriptionDetailDto?> Handle(UpdateSubscriptionStatusCommand request, CancellationToken cancellationToken)
{
var ignoreTenantFilter = SubscriptionTenantAccess.ShouldIgnoreTenantFilter(httpContextAccessor);
// 1. 查询订阅
var subscription = await subscriptionRepository.FindByIdAsync(
request.SubscriptionId,
cancellationToken,
ignoreTenantFilter: ignoreTenantFilter);
cancellationToken);
if (subscription == null)
{