feat: 新增租户端商户中心聚合接口
All checks were successful
Build and Deploy TenantApi / build-and-deploy (push) Successful in 40s
All checks were successful
Build and Deploy TenantApi / build-and-deploy (push) Successful in 40s
提供 /merchant/info 聚合查询,返回商户主体、资质、合同、员工与日志信息,满足租户端商户中心页面一次加载全部相关数据。
This commit is contained in:
@@ -0,0 +1,35 @@
|
|||||||
|
using MediatR;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using TakeoutSaaS.Application.App.Merchants.Dto;
|
||||||
|
using TakeoutSaaS.Application.App.Merchants.Queries;
|
||||||
|
using TakeoutSaaS.Shared.Abstractions.Results;
|
||||||
|
using TakeoutSaaS.Shared.Web.Api;
|
||||||
|
|
||||||
|
namespace TakeoutSaaS.TenantApi.Controllers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 租户端商户中心。
|
||||||
|
/// </summary>
|
||||||
|
[ApiVersion("1.0")]
|
||||||
|
[Authorize]
|
||||||
|
[Route("api/tenant/v{version:apiVersion}/merchant")]
|
||||||
|
public sealed class MerchantController(IMediator mediator) : BaseApiController
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前登录用户对应的商户中心信息。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken">取消标记。</param>
|
||||||
|
/// <returns>商户中心聚合信息。</returns>
|
||||||
|
[HttpGet("info")]
|
||||||
|
[ProducesResponseType(typeof(ApiResponse<CurrentMerchantCenterDto>), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(typeof(ApiResponse<CurrentMerchantCenterDto>), StatusCodes.Status401Unauthorized)]
|
||||||
|
public async Task<ApiResponse<CurrentMerchantCenterDto>> GetInfo(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// 1. 查询当前商户中心信息
|
||||||
|
var info = await mediator.Send(new GetCurrentMerchantCenterQuery(), cancellationToken);
|
||||||
|
|
||||||
|
// 2. 返回聚合信息
|
||||||
|
return ApiResponse<CurrentMerchantCenterDto>.Ok(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using TakeoutSaaS.Application.App.Stores.Dto;
|
||||||
|
|
||||||
|
namespace TakeoutSaaS.Application.App.Merchants.Dto;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前租户商户中心聚合信息。
|
||||||
|
/// </summary>
|
||||||
|
public sealed class CurrentMerchantCenterDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 商户主体详情。
|
||||||
|
/// </summary>
|
||||||
|
public MerchantDetailDto Merchant { get; init; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 商户证照列表。
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<MerchantDocumentDto> Documents { get; init; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 商户合同列表。
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<MerchantContractDto> Contracts { get; init; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 商户员工列表。
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<StoreStaffDto> Staffs { get; init; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 商户审核日志列表。
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<MerchantAuditLogDto> AuditLogs { get; init; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 商户变更日志列表。
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<MerchantChangeLogDto> ChangeLogs { get; init; } = [];
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
using MediatR;
|
||||||
|
using TakeoutSaaS.Application.App.Merchants.Dto;
|
||||||
|
using TakeoutSaaS.Application.App.Merchants.Queries;
|
||||||
|
using TakeoutSaaS.Application.App.Stores;
|
||||||
|
using TakeoutSaaS.Application.App.Stores.Dto;
|
||||||
|
using TakeoutSaaS.Domain.Identity.Repositories;
|
||||||
|
using TakeoutSaaS.Domain.Merchants.Repositories;
|
||||||
|
using TakeoutSaaS.Domain.Stores.Repositories;
|
||||||
|
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||||
|
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||||
|
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||||
|
using TakeoutSaaS.Shared.Abstractions.Security;
|
||||||
|
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||||
|
|
||||||
|
namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前商户中心信息查询处理器。
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GetCurrentMerchantCenterQueryHandler(
|
||||||
|
ICurrentUserAccessor currentUserAccessor,
|
||||||
|
ITenantProvider tenantProvider,
|
||||||
|
IIdentityUserRepository identityUserRepository,
|
||||||
|
IMerchantRepository merchantRepository,
|
||||||
|
IStoreRepository storeRepository,
|
||||||
|
ITenantRepository tenantRepository)
|
||||||
|
: IRequestHandler<GetCurrentMerchantCenterQuery, CurrentMerchantCenterDto>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前登录用户可访问的商户中心完整信息。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">查询请求。</param>
|
||||||
|
/// <param name="cancellationToken">取消标记。</param>
|
||||||
|
/// <returns>商户中心聚合信息。</returns>
|
||||||
|
public async Task<CurrentMerchantCenterDto> Handle(GetCurrentMerchantCenterQuery request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// 1. 校验登录上下文
|
||||||
|
var currentUserId = currentUserAccessor.UserId;
|
||||||
|
if (currentUserId <= 0)
|
||||||
|
{
|
||||||
|
throw new BusinessException(ErrorCodes.Unauthorized, "未登录或登录已过期");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 校验租户上下文
|
||||||
|
var currentTenantId = tenantProvider.GetCurrentTenantId();
|
||||||
|
if (currentTenantId <= 0)
|
||||||
|
{
|
||||||
|
throw new BusinessException(ErrorCodes.BadRequest, "缺少租户标识,请在 Header 指定租户");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 校验当前用户归属
|
||||||
|
var currentUser = await identityUserRepository.FindByIdAsync(currentUserId, cancellationToken)
|
||||||
|
?? throw new BusinessException(ErrorCodes.NotFound, "用户不存在");
|
||||||
|
if (currentUser.TenantId != currentTenantId)
|
||||||
|
{
|
||||||
|
throw new BusinessException(ErrorCodes.Unauthorized, "无权访问当前租户的商户信息");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 解析当前用户可访问商户
|
||||||
|
var merchantId = await ResolveMerchantIdAsync(currentUser.MerchantId, currentTenantId, cancellationToken);
|
||||||
|
|
||||||
|
// 5. 读取商户主体与租户信息
|
||||||
|
var merchant = await merchantRepository.FindByIdAsync(merchantId, currentTenantId, cancellationToken)
|
||||||
|
?? throw new BusinessException(ErrorCodes.NotFound, "商户不存在");
|
||||||
|
var tenant = await tenantRepository.FindByIdAsync(currentTenantId, cancellationToken);
|
||||||
|
|
||||||
|
// 6. 读取商户关联数据
|
||||||
|
var stores = await storeRepository.GetByMerchantIdAsync(merchantId, currentTenantId, cancellationToken);
|
||||||
|
var staffs = await merchantRepository.GetStaffAsync(merchantId, currentTenantId, cancellationToken);
|
||||||
|
var documents = await merchantRepository.GetDocumentsAsync(merchantId, currentTenantId, cancellationToken);
|
||||||
|
var contracts = await merchantRepository.GetContractsAsync(merchantId, currentTenantId, cancellationToken);
|
||||||
|
var auditLogs = await merchantRepository.GetAuditLogsAsync(merchantId, currentTenantId, cancellationToken);
|
||||||
|
var changeLogs = await merchantRepository.GetChangeLogsAsync(merchantId, currentTenantId, null, cancellationToken);
|
||||||
|
|
||||||
|
// 7. 组装并返回聚合结果
|
||||||
|
var storeDtos = MerchantMapping.ToStoreDtos(stores);
|
||||||
|
var staffDtos = staffs.Select(StoreMapping.ToDto).ToList();
|
||||||
|
return new CurrentMerchantCenterDto
|
||||||
|
{
|
||||||
|
Merchant = MerchantMapping.ToDetailDto(merchant, tenant?.Name, storeDtos),
|
||||||
|
Staffs = staffDtos,
|
||||||
|
Documents = MerchantMapping.ToDocumentDtos(documents),
|
||||||
|
Contracts = MerchantMapping.ToContractDtos(contracts),
|
||||||
|
AuditLogs = auditLogs.Select(MerchantMapping.ToDto).ToList(),
|
||||||
|
ChangeLogs = changeLogs.Select(MerchantMapping.ToDto).ToList()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解析当前用户对应的商户标识。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="currentUserMerchantId">当前用户绑定的商户 ID。</param>
|
||||||
|
/// <param name="tenantId">当前租户 ID。</param>
|
||||||
|
/// <param name="cancellationToken">取消标记。</param>
|
||||||
|
/// <returns>商户 ID。</returns>
|
||||||
|
private async Task<long> ResolveMerchantIdAsync(long? currentUserMerchantId, long tenantId, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// 1. 优先使用用户显式绑定的商户
|
||||||
|
if (currentUserMerchantId is > 0)
|
||||||
|
{
|
||||||
|
return currentUserMerchantId.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 兜底读取租户下最新商户
|
||||||
|
var merchants = await merchantRepository.SearchAsync(tenantId, status: null, cancellationToken);
|
||||||
|
var merchantId = merchants.FirstOrDefault()?.Id;
|
||||||
|
if (!merchantId.HasValue || merchantId.Value <= 0)
|
||||||
|
{
|
||||||
|
throw new BusinessException(ErrorCodes.NotFound, "当前租户尚未创建商户信息");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 返回商户标识
|
||||||
|
return merchantId.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using MediatR;
|
||||||
|
using TakeoutSaaS.Application.App.Merchants.Dto;
|
||||||
|
|
||||||
|
namespace TakeoutSaaS.Application.App.Merchants.Queries;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前登录用户可访问的商户中心聚合信息。
|
||||||
|
/// </summary>
|
||||||
|
public sealed record GetCurrentMerchantCenterQuery : IRequest<CurrentMerchantCenterDto>;
|
||||||
Reference in New Issue
Block a user