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
};
}
}