feat: 完成租户个人中心 API 首版实现
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 获取个人中心总览处理器。
|
||||
/// </summary>
|
||||
public sealed class GetPersonalOverviewQueryHandler(
|
||||
PersonalContextService personalContextService,
|
||||
PersonalMaskingService personalMaskingService,
|
||||
PersonalModuleStatusService moduleStatusService,
|
||||
IIdentityUserRepository identityUserRepository,
|
||||
ITenantRepository tenantRepository,
|
||||
ITenantPackageRepository tenantPackageRepository,
|
||||
IMerchantRepository merchantRepository,
|
||||
IMediator mediator)
|
||||
: IRequestHandler<GetPersonalOverviewQuery, PersonalOverviewDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// 处理查询。
|
||||
/// </summary>
|
||||
/// <param name="request">查询请求。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>总览结果。</returns>
|
||||
public async Task<PersonalOverviewDto> Handle(GetPersonalOverviewQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 获取必需上下文
|
||||
var context = personalContextService.GetRequiredContext();
|
||||
var traceId = context.TraceId;
|
||||
|
||||
// 2. 初始化总览容器
|
||||
var moduleStatuses = new List<PersonalModuleStatusDto>();
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user