using MediatR; using TakeoutSaaS.Application.App.Personal.Dto; using TakeoutSaaS.Application.App.Personal.Queries; using TakeoutSaaS.Application.App.Personal.Services; using TakeoutSaaS.Domain.Identity.Enums; using TakeoutSaaS.Domain.Identity.Repositories; using TakeoutSaaS.Domain.Merchants.Repositories; using TakeoutSaaS.Domain.Tenants.Repositories; using TakeoutSaaS.Shared.Abstractions.Constants; using TakeoutSaaS.Shared.Abstractions.Exceptions; namespace TakeoutSaaS.Application.App.Personal.Handlers; /// /// 获取个人中心总览处理器。 /// public sealed class GetPersonalOverviewQueryHandler( PersonalContextService personalContextService, PersonalMaskingService personalMaskingService, PersonalModuleStatusService moduleStatusService, IIdentityUserRepository identityUserRepository, ITenantRepository tenantRepository, ITenantPackageRepository tenantPackageRepository, IMerchantRepository merchantRepository, IMediator mediator) : IRequestHandler { /// /// 处理查询。 /// /// 查询请求。 /// 取消标记。 /// 总览结果。 public async Task Handle(GetPersonalOverviewQuery request, CancellationToken cancellationToken) { // 1. 获取必需上下文 var context = personalContextService.GetRequiredContext(); var traceId = context.TraceId; // 2. 初始化总览容器 var moduleStatuses = new List(); PersonalAccountProfileDto? accountProfile = null; PersonalSecuritySnapshotDto? securitySnapshot = null; PersonalRolePermissionSummaryDto? roleSummary = null; PersonalTenantAffiliationDto? tenantAffiliation = null; // 3. 加载账号与安全模块 var user = await identityUserRepository.FindByIdAsync(context.UserId, cancellationToken) ?? throw new BusinessException(ErrorCodes.NotFound, "用户不存在"); if (user.TenantId != context.TenantId) { throw new BusinessException(ErrorCodes.Forbidden, "无权访问其他租户用户数据"); } accountProfile = new PersonalAccountProfileDto { UserId = user.Id, Account = user.Account, DisplayName = user.DisplayName, AvatarUrl = user.Avatar, PhoneMasked = personalMaskingService.MaskPhone(user.Phone), EmailMasked = personalMaskingService.MaskEmail(user.Email), RegisteredAt = user.CreatedAt }; securitySnapshot = new PersonalSecuritySnapshotDto { LastLoginAt = user.LastLoginAt, FailedLoginCount = user.FailedLoginCount, IsLocked = user.Status == IdentityUserStatus.Locked || (user.LockedUntil.HasValue && user.LockedUntil > DateTime.UtcNow), LockedUntil = user.LockedUntil, IsForceChangePassword = user.MustChangePassword }; moduleStatuses.Add(moduleStatusService.BuildOk("accountSecurity", traceId)); // 4. 加载角色权限模块(失败可降级) try { roleSummary = await mediator.Send(new GetPersonalRolesQuery(), cancellationToken); moduleStatuses.Add(moduleStatusService.BuildOk("roleSummary", traceId)); } catch (Exception ex) { moduleStatuses.Add(moduleStatusService.BuildIssue("roleSummary", "degraded", ErrorCodes.InternalServerError.ToString(), ex.Message, traceId)); } // 5. 加载租户归属模块(失败可降级) try { var tenant = await tenantRepository.FindByIdAsync(context.TenantId, cancellationToken) ?? throw new BusinessException(ErrorCodes.NotFound, "租户不存在"); var subscription = await tenantRepository.GetActiveSubscriptionAsync(context.TenantId, cancellationToken); string? packageName = null; if (subscription is not null) { var package = await tenantPackageRepository.FindByIdAsync(subscription.TenantPackageId, cancellationToken); packageName = package?.Name; } string? merchantName = null; string merchantStatus = "unknown"; if (user.MerchantId is > 0) { var merchant = await merchantRepository.FindByIdAsync(user.MerchantId.Value, context.TenantId, cancellationToken); merchantName = merchant?.BrandName; merchantStatus = merchant?.Status.ToString().ToLowerInvariant() ?? "unknown"; } tenantAffiliation = new PersonalTenantAffiliationDto { TenantId = tenant.Id, TenantName = tenant.Name, MerchantId = user.MerchantId, MerchantName = merchantName, MerchantStatus = merchantStatus, PackageName = packageName, SubscriptionExpireAt = subscription?.EffectiveTo ?? tenant.EffectiveTo }; moduleStatuses.Add(moduleStatusService.BuildOk("tenantAffiliation", traceId)); } catch (Exception ex) { moduleStatuses.Add(moduleStatusService.BuildIssue("tenantAffiliation", "degraded", ErrorCodes.InternalServerError.ToString(), ex.Message, traceId)); } // 6. 计算总体状态并返回 var hasAnyData = accountProfile is not null || securitySnapshot is not null || roleSummary is not null || tenantAffiliation is not null; var requestId = string.IsNullOrWhiteSpace(traceId) ? Guid.NewGuid().ToString("N") : traceId; var overallStatus = moduleStatusService.ResolveOverallStatus(moduleStatuses, hasAnyData); return new PersonalOverviewDto { RequestId = requestId, OverallStatus = overallStatus, AccountProfile = accountProfile, SecuritySnapshot = securitySnapshot, RoleSummary = roleSummary, TenantAffiliation = tenantAffiliation, QuotaSummary = null, ModuleStatuses = moduleStatuses }; } }