feat: 商户模块移除租户上下文依赖
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -22,15 +23,18 @@ public sealed class MerchantCategoriesController(IMediator mediator) : BaseApiCo
|
||||
/// <summary>
|
||||
/// 列出所有类目。
|
||||
/// </summary>
|
||||
/// <param name="tenantId">租户 ID。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>类目列表。</returns>
|
||||
[HttpGet]
|
||||
[PermissionAuthorize("merchant_category:read")]
|
||||
[ProducesResponseType(typeof(ApiResponse<IReadOnlyList<MerchantCategoryDto>>), StatusCodes.Status200OK)]
|
||||
public async Task<ApiResponse<IReadOnlyList<MerchantCategoryDto>>> List(CancellationToken cancellationToken)
|
||||
public async Task<ApiResponse<IReadOnlyList<MerchantCategoryDto>>> List(
|
||||
[FromQuery, Range(1, long.MaxValue)] long tenantId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 查询所有类目
|
||||
var result = await mediator.Send(new ListMerchantCategoriesQuery(), cancellationToken);
|
||||
var result = await mediator.Send(new ListMerchantCategoriesQuery { TenantId = tenantId }, cancellationToken);
|
||||
|
||||
// 2. 返回类目列表
|
||||
return ApiResponse<IReadOnlyList<MerchantCategoryDto>>.Ok(result);
|
||||
@@ -39,15 +43,20 @@ public sealed class MerchantCategoriesController(IMediator mediator) : BaseApiCo
|
||||
/// <summary>
|
||||
/// 新增类目。
|
||||
/// </summary>
|
||||
/// <param name="tenantId">租户 ID。</param>
|
||||
/// <param name="command">创建命令。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>创建的类目。</returns>
|
||||
[HttpPost]
|
||||
[PermissionAuthorize("merchant_category:create")]
|
||||
[ProducesResponseType(typeof(ApiResponse<MerchantCategoryDto>), StatusCodes.Status200OK)]
|
||||
public async Task<ApiResponse<MerchantCategoryDto>> Create([FromBody] CreateMerchantCategoryCommand command, CancellationToken cancellationToken)
|
||||
public async Task<ApiResponse<MerchantCategoryDto>> Create(
|
||||
[FromQuery, Range(1, long.MaxValue)] long tenantId,
|
||||
[FromBody] CreateMerchantCategoryCommand command,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 创建类目
|
||||
command = command with { TenantId = tenantId };
|
||||
var result = await mediator.Send(command, cancellationToken);
|
||||
|
||||
// 2. 返回创建结果
|
||||
@@ -57,6 +66,7 @@ public sealed class MerchantCategoriesController(IMediator mediator) : BaseApiCo
|
||||
/// <summary>
|
||||
/// 删除类目。
|
||||
/// </summary>
|
||||
/// <param name="tenantId">租户 ID。</param>
|
||||
/// <param name="categoryId">类目 ID。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>删除结果,未找到则返回错误。</returns>
|
||||
@@ -64,10 +74,13 @@ public sealed class MerchantCategoriesController(IMediator mediator) : BaseApiCo
|
||||
[PermissionAuthorize("merchant_category:delete")]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||||
public async Task<ApiResponse<object>> Delete(long categoryId, CancellationToken cancellationToken)
|
||||
public async Task<ApiResponse<object>> Delete(
|
||||
[FromQuery, Range(1, long.MaxValue)] long tenantId,
|
||||
long categoryId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 执行删除
|
||||
var success = await mediator.Send(new DeleteMerchantCategoryCommand(categoryId), cancellationToken);
|
||||
var success = await mediator.Send(new DeleteMerchantCategoryCommand(tenantId, categoryId), cancellationToken);
|
||||
|
||||
// 2. 返回删除结果或 404
|
||||
return success
|
||||
@@ -78,15 +91,20 @@ public sealed class MerchantCategoriesController(IMediator mediator) : BaseApiCo
|
||||
/// <summary>
|
||||
/// 批量调整类目排序。
|
||||
/// </summary>
|
||||
/// <param name="tenantId">租户 ID。</param>
|
||||
/// <param name="command">排序命令。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>执行结果。</returns>
|
||||
[HttpPost("reorder")]
|
||||
[PermissionAuthorize("merchant_category:update")]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
|
||||
public async Task<ApiResponse<object>> Reorder([FromBody] ReorderMerchantCategoriesCommand command, CancellationToken cancellationToken)
|
||||
public async Task<ApiResponse<object>> Reorder(
|
||||
[FromQuery, Range(1, long.MaxValue)] long tenantId,
|
||||
[FromBody] ReorderMerchantCategoriesCommand command,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 执行排序调整
|
||||
command = command with { TenantId = tenantId };
|
||||
await mediator.Send(command, cancellationToken);
|
||||
|
||||
// 2. 返回成功结果
|
||||
|
||||
@@ -2,6 +2,7 @@ using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using TakeoutSaaS.Application.App.Merchants.Commands;
|
||||
using TakeoutSaaS.Application.App.Merchants.Dto;
|
||||
using TakeoutSaaS.Application.App.Merchants.Queries;
|
||||
@@ -436,10 +437,12 @@ public sealed class MerchantsController(IMediator mediator) : BaseApiController
|
||||
[HttpGet("categories")]
|
||||
[PermissionAuthorize("merchant:read")]
|
||||
[ProducesResponseType(typeof(ApiResponse<IReadOnlyList<string>>), StatusCodes.Status200OK)]
|
||||
public async Task<ApiResponse<IReadOnlyList<string>>> Categories(CancellationToken cancellationToken)
|
||||
public async Task<ApiResponse<IReadOnlyList<string>>> Categories(
|
||||
[FromQuery, Range(1, long.MaxValue)] long tenantId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 查询可选类目
|
||||
var result = await mediator.Send(new GetMerchantCategoriesQuery(), cancellationToken);
|
||||
var result = await mediator.Send(new GetMerchantCategoriesQuery(tenantId), cancellationToken);
|
||||
|
||||
// 2. 返回类目列表
|
||||
return ApiResponse<IReadOnlyList<string>>.Ok(result);
|
||||
|
||||
@@ -7,7 +7,27 @@ namespace TakeoutSaaS.Application.App.Merchants.Commands;
|
||||
/// <summary>
|
||||
/// 新增商户类目。
|
||||
/// </summary>
|
||||
public sealed record CreateMerchantCategoryCommand(
|
||||
[property: Required, MaxLength(64)] string Name,
|
||||
int? DisplayOrder,
|
||||
bool IsActive = true) : IRequest<MerchantCategoryDto>;
|
||||
public sealed record CreateMerchantCategoryCommand : IRequest<MerchantCategoryDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// 租户 ID。
|
||||
/// </summary>
|
||||
[Range(1, long.MaxValue)]
|
||||
public long TenantId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 类目名称。
|
||||
/// </summary>
|
||||
[Required, MaxLength(64)]
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 排序(为空则自动追加到末尾)。
|
||||
/// </summary>
|
||||
public int? DisplayOrder { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用。
|
||||
/// </summary>
|
||||
public bool IsActive { get; init; } = true;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,12 @@ namespace TakeoutSaaS.Application.App.Merchants.Commands;
|
||||
/// </summary>
|
||||
public sealed class CreateMerchantCommand : IRequest<MerchantDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// 所属租户 ID。
|
||||
/// </summary>
|
||||
[Range(1, long.MaxValue)]
|
||||
public long TenantId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 品牌名称。
|
||||
/// </summary>
|
||||
|
||||
@@ -6,4 +6,6 @@ namespace TakeoutSaaS.Application.App.Merchants.Commands;
|
||||
/// <summary>
|
||||
/// 删除商户类目。
|
||||
/// </summary>
|
||||
public sealed record DeleteMerchantCategoryCommand([property: Required] long CategoryId) : IRequest<bool>;
|
||||
public sealed record DeleteMerchantCategoryCommand(
|
||||
[property: Range(1, long.MaxValue)] long TenantId,
|
||||
[property: Required] long CategoryId) : IRequest<bool>;
|
||||
|
||||
@@ -6,8 +6,20 @@ namespace TakeoutSaaS.Application.App.Merchants.Commands;
|
||||
/// <summary>
|
||||
/// 调整类目排序。
|
||||
/// </summary>
|
||||
public sealed record ReorderMerchantCategoriesCommand(
|
||||
[property: Required, MinLength(1)] IReadOnlyList<MerchantCategoryOrderItem> Items) : IRequest<bool>;
|
||||
public sealed record ReorderMerchantCategoriesCommand : IRequest<bool>
|
||||
{
|
||||
/// <summary>
|
||||
/// 租户 ID。
|
||||
/// </summary>
|
||||
[Range(1, long.MaxValue)]
|
||||
public long TenantId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序条目。
|
||||
/// </summary>
|
||||
[Required, MinLength(1)]
|
||||
public IReadOnlyList<MerchantCategoryOrderItem> Items { get; init; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 类目排序条目。
|
||||
|
||||
@@ -8,7 +8,6 @@ using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||
using TakeoutSaaS.Shared.Abstractions.Ids;
|
||||
using TakeoutSaaS.Shared.Abstractions.Security;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
|
||||
@@ -17,7 +16,6 @@ namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
/// </summary>
|
||||
public sealed class AddMerchantDocumentCommandHandler(
|
||||
IMerchantRepository merchantRepository,
|
||||
ITenantProvider tenantProvider,
|
||||
IIdGenerator idGenerator,
|
||||
ICurrentUserAccessor currentUserAccessor)
|
||||
: IRequestHandler<AddMerchantDocumentCommand, MerchantDocumentDto>
|
||||
@@ -30,15 +28,16 @@ public sealed class AddMerchantDocumentCommandHandler(
|
||||
/// <returns>证照 DTO。</returns>
|
||||
public async Task<MerchantDocumentDto> Handle(AddMerchantDocumentCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 获取租户并查询商户
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, tenantId, cancellationToken)
|
||||
// 1. 查询商户并解析租户
|
||||
var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken)
|
||||
?? throw new BusinessException(ErrorCodes.NotFound, "商户不存在");
|
||||
var tenantId = merchant.TenantId;
|
||||
|
||||
// 2. 构建证照记录
|
||||
// 2. (空行后) 构建证照记录并写入租户
|
||||
var document = new MerchantDocument
|
||||
{
|
||||
Id = idGenerator.NextId(),
|
||||
TenantId = tenantId,
|
||||
MerchantId = merchant.Id,
|
||||
DocumentType = request.DocumentType,
|
||||
Status = MerchantDocumentStatus.Pending,
|
||||
@@ -48,7 +47,7 @@ public sealed class AddMerchantDocumentCommandHandler(
|
||||
ExpiresAt = request.ExpiresAt
|
||||
};
|
||||
|
||||
// 3. 持久化与审计
|
||||
// 3. (空行后) 持久化与审计
|
||||
await merchantRepository.AddDocumentAsync(document, cancellationToken);
|
||||
await merchantRepository.AddAuditLogAsync(new MerchantAuditLog
|
||||
{
|
||||
|
||||
@@ -5,7 +5,6 @@ using TakeoutSaaS.Domain.Merchants.Entities;
|
||||
using TakeoutSaaS.Domain.Merchants.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
|
||||
@@ -13,8 +12,7 @@ namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
/// 创建类目处理器。
|
||||
/// </summary>
|
||||
public sealed class CreateMerchantCategoryCommandHandler(
|
||||
IMerchantCategoryRepository categoryRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
IMerchantCategoryRepository categoryRepository)
|
||||
: IRequestHandler<CreateMerchantCategoryCommand, MerchantCategoryDto>
|
||||
{
|
||||
/// <summary>
|
||||
@@ -25,29 +23,30 @@ public sealed class CreateMerchantCategoryCommandHandler(
|
||||
/// <returns>类目 DTO。</returns>
|
||||
public async Task<MerchantCategoryDto> Handle(CreateMerchantCategoryCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 获取租户上下文
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
// 1. 解析租户与规范化名称
|
||||
var tenantId = request.TenantId;
|
||||
var normalizedName = request.Name.Trim();
|
||||
|
||||
// 2. 检查重名
|
||||
// 2. (空行后) 检查重名
|
||||
if (await categoryRepository.ExistsAsync(normalizedName, tenantId, cancellationToken))
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.Conflict, $"类目“{normalizedName}”已存在");
|
||||
}
|
||||
|
||||
// 3. 计算排序
|
||||
// 3. (空行后) 计算排序
|
||||
var categories = await categoryRepository.ListAsync(tenantId, cancellationToken);
|
||||
var targetOrder = request.DisplayOrder ?? (categories.Count == 0 ? 1 : categories.Max(x => x.DisplayOrder) + 1);
|
||||
|
||||
// 4. 构建实体
|
||||
// 4. (空行后) 构建实体并写入租户
|
||||
var entity = new MerchantCategory
|
||||
{
|
||||
TenantId = tenantId,
|
||||
Name = normalizedName,
|
||||
DisplayOrder = targetOrder,
|
||||
IsActive = request.IsActive
|
||||
};
|
||||
|
||||
// 5. 持久化并返回
|
||||
// 5. (空行后) 持久化并返回
|
||||
await categoryRepository.AddAsync(entity, cancellationToken);
|
||||
await categoryRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ public sealed class CreateMerchantCommandHandler(IMerchantRepository merchantRep
|
||||
// 1. 构建商户实体
|
||||
var merchant = new Merchant
|
||||
{
|
||||
TenantId = request.TenantId,
|
||||
BrandName = request.BrandName.Trim(),
|
||||
BrandAlias = request.BrandAlias?.Trim(),
|
||||
LogoUrl = request.LogoUrl?.Trim(),
|
||||
|
||||
@@ -8,7 +8,6 @@ using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||
using TakeoutSaaS.Shared.Abstractions.Ids;
|
||||
using TakeoutSaaS.Shared.Abstractions.Security;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
|
||||
@@ -17,7 +16,6 @@ namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
/// </summary>
|
||||
public sealed class CreateMerchantContractCommandHandler(
|
||||
IMerchantRepository merchantRepository,
|
||||
ITenantProvider tenantProvider,
|
||||
IIdGenerator idGenerator,
|
||||
ICurrentUserAccessor currentUserAccessor)
|
||||
: IRequestHandler<CreateMerchantContractCommand, MerchantContractDto>
|
||||
@@ -36,15 +34,16 @@ public sealed class CreateMerchantContractCommandHandler(
|
||||
throw new BusinessException(ErrorCodes.BadRequest, "合同结束时间必须晚于开始时间");
|
||||
}
|
||||
|
||||
// 2. 查询商户
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, tenantId, cancellationToken)
|
||||
// 2. 查询商户并解析租户
|
||||
var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken)
|
||||
?? throw new BusinessException(ErrorCodes.NotFound, "商户不存在");
|
||||
var tenantId = merchant.TenantId;
|
||||
|
||||
// 3. 构建合同
|
||||
// 3. (空行后) 构建合同并写入租户
|
||||
var contract = new MerchantContract
|
||||
{
|
||||
Id = idGenerator.NextId(),
|
||||
TenantId = tenantId,
|
||||
MerchantId = merchant.Id,
|
||||
ContractNumber = request.ContractNumber.Trim(),
|
||||
StartDate = request.StartDate,
|
||||
@@ -52,7 +51,7 @@ public sealed class CreateMerchantContractCommandHandler(
|
||||
FileUrl = request.FileUrl.Trim()
|
||||
};
|
||||
|
||||
// 4. 持久化与审计
|
||||
// 4. (空行后) 持久化与审计
|
||||
await merchantRepository.AddContractAsync(contract, cancellationToken);
|
||||
await merchantRepository.AddAuditLogAsync(new MerchantAuditLog
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Merchants.Commands;
|
||||
using TakeoutSaaS.Domain.Merchants.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
|
||||
@@ -9,8 +8,7 @@ namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
/// 删除类目处理器。
|
||||
/// </summary>
|
||||
public sealed class DeleteMerchantCategoryCommandHandler(
|
||||
IMerchantCategoryRepository categoryRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
IMerchantCategoryRepository categoryRepository)
|
||||
: IRequestHandler<DeleteMerchantCategoryCommand, bool>
|
||||
{
|
||||
/// <summary>
|
||||
@@ -21,16 +19,15 @@ public sealed class DeleteMerchantCategoryCommandHandler(
|
||||
/// <returns>执行结果。</returns>
|
||||
public async Task<bool> Handle(DeleteMerchantCategoryCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 获取租户上下文
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var existing = await categoryRepository.FindByIdAsync(request.CategoryId, tenantId, cancellationToken);
|
||||
// 1. 查询类目
|
||||
var existing = await categoryRepository.FindByIdAsync(request.CategoryId, request.TenantId, cancellationToken);
|
||||
|
||||
if (existing == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 删除并保存
|
||||
// 2. (空行后) 删除并保存
|
||||
await categoryRepository.RemoveAsync(existing, cancellationToken);
|
||||
await categoryRepository.SaveChangesAsync(cancellationToken);
|
||||
return true;
|
||||
|
||||
@@ -2,7 +2,6 @@ using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TakeoutSaaS.Application.App.Merchants.Commands;
|
||||
using TakeoutSaaS.Domain.Merchants.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
|
||||
@@ -11,23 +10,21 @@ namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
/// </summary>
|
||||
public sealed class DeleteMerchantCommandHandler(
|
||||
IMerchantRepository merchantRepository,
|
||||
ITenantProvider tenantProvider,
|
||||
ILogger<DeleteMerchantCommandHandler> logger)
|
||||
: IRequestHandler<DeleteMerchantCommand, bool>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> Handle(DeleteMerchantCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 校验存在性
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var existing = await merchantRepository.FindByIdAsync(request.MerchantId, tenantId, cancellationToken);
|
||||
// 1. 校验存在性(跨租户)
|
||||
var existing = await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken);
|
||||
if (existing == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 删除
|
||||
await merchantRepository.DeleteMerchantAsync(request.MerchantId, tenantId, cancellationToken);
|
||||
// 2. (空行后) 删除
|
||||
await merchantRepository.DeleteMerchantAsync(request.MerchantId, existing.TenantId, cancellationToken);
|
||||
await merchantRepository.SaveChangesAsync(cancellationToken);
|
||||
logger.LogInformation("删除商户 {MerchantId}", request.MerchantId);
|
||||
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Merchants.Queries;
|
||||
using TakeoutSaaS.Application.Identity;
|
||||
using TakeoutSaaS.Application.Identity.Abstractions;
|
||||
using TakeoutSaaS.Domain.Merchants.Repositories;
|
||||
using TakeoutSaaS.Domain.Merchants.Services;
|
||||
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;
|
||||
|
||||
@@ -20,36 +16,24 @@ public sealed class ExportMerchantPdfQueryHandler(
|
||||
IMerchantRepository merchantRepository,
|
||||
IStoreRepository storeRepository,
|
||||
ITenantRepository tenantRepository,
|
||||
IMerchantExportService exportService,
|
||||
ITenantProvider tenantProvider,
|
||||
ICurrentUserAccessor currentUserAccessor,
|
||||
IAdminAuthService adminAuthService)
|
||||
IMerchantExportService exportService)
|
||||
: IRequestHandler<ExportMerchantPdfQuery, byte[]>
|
||||
{
|
||||
public async Task<byte[]> Handle(ExportMerchantPdfQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var currentTenantId = tenantProvider.GetCurrentTenantId();
|
||||
var operatorProfile = await adminAuthService.GetProfileAsync(currentUserAccessor.UserId, cancellationToken);
|
||||
var isSuperAdmin = IdentityUserAccess.IsSuperAdmin(operatorProfile);
|
||||
|
||||
var merchant = isSuperAdmin
|
||||
? await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken)
|
||||
: await merchantRepository.FindByIdAsync(request.MerchantId, currentTenantId, cancellationToken);
|
||||
|
||||
// 1. 查询商户(跨租户)
|
||||
var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken);
|
||||
if (merchant == null)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.NotFound, "商户不存在");
|
||||
}
|
||||
|
||||
if (!isSuperAdmin && merchant.TenantId != currentTenantId)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.Forbidden, "禁止导出其他租户商户");
|
||||
}
|
||||
|
||||
// 2. (空行后) 查询关联数据
|
||||
var stores = await storeRepository.GetByMerchantIdAsync(merchant.Id, merchant.TenantId, cancellationToken);
|
||||
var auditLogs = await merchantRepository.GetAuditLogsAsync(merchant.Id, merchant.TenantId, cancellationToken);
|
||||
var tenant = await tenantRepository.FindByIdAsync(merchant.TenantId, cancellationToken);
|
||||
|
||||
// 3. (空行后) 导出 PDF
|
||||
return await exportService.ExportToPdfAsync(merchant, tenant?.Name, stores, auditLogs, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Merchants.Dto;
|
||||
using TakeoutSaaS.Application.App.Merchants.Queries;
|
||||
using TakeoutSaaS.Application.Identity;
|
||||
using TakeoutSaaS.Application.Identity.Abstractions;
|
||||
using TakeoutSaaS.Domain.Merchants.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;
|
||||
|
||||
@@ -15,10 +11,7 @@ namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
/// 商户审核历史处理器。
|
||||
/// </summary>
|
||||
public sealed class GetMerchantAuditHistoryQueryHandler(
|
||||
IMerchantRepository merchantRepository,
|
||||
ITenantProvider tenantProvider,
|
||||
ICurrentUserAccessor currentUserAccessor,
|
||||
IAdminAuthService adminAuthService)
|
||||
IMerchantRepository merchantRepository)
|
||||
: IRequestHandler<GetMerchantAuditHistoryQuery, IReadOnlyList<MerchantAuditLogDto>>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
@@ -26,24 +19,14 @@ public sealed class GetMerchantAuditHistoryQueryHandler(
|
||||
GetMerchantAuditHistoryQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var currentTenantId = tenantProvider.GetCurrentTenantId();
|
||||
var operatorProfile = await adminAuthService.GetProfileAsync(currentUserAccessor.UserId, cancellationToken);
|
||||
var isSuperAdmin = IdentityUserAccess.IsSuperAdmin(operatorProfile);
|
||||
|
||||
var merchant = isSuperAdmin
|
||||
? await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken)
|
||||
: await merchantRepository.FindByIdAsync(request.MerchantId, currentTenantId, cancellationToken);
|
||||
|
||||
// 1. 查询商户(跨租户)
|
||||
var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken);
|
||||
if (merchant == null)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.NotFound, "商户不存在");
|
||||
}
|
||||
|
||||
if (!isSuperAdmin && merchant.TenantId != currentTenantId)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.Forbidden, "禁止访问其他租户的商户审核历史");
|
||||
}
|
||||
|
||||
// 2. (空行后) 查询审核历史
|
||||
var logs = await merchantRepository.GetAuditLogsAsync(merchant.Id, merchant.TenantId, cancellationToken);
|
||||
return logs.Select(MerchantMapping.ToDto).ToList();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using TakeoutSaaS.Application.App.Merchants.Dto;
|
||||
using TakeoutSaaS.Application.App.Merchants.Queries;
|
||||
using TakeoutSaaS.Domain.Merchants.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Results;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
|
||||
@@ -11,8 +10,7 @@ namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
/// 读取商户审核日志。
|
||||
/// </summary>
|
||||
public sealed class GetMerchantAuditLogsQueryHandler(
|
||||
IMerchantRepository merchantRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
IMerchantRepository merchantRepository)
|
||||
: IRequestHandler<GetMerchantAuditLogsQuery, PagedResult<MerchantAuditLogDto>>
|
||||
{
|
||||
/// <summary>
|
||||
@@ -23,19 +21,25 @@ public sealed class GetMerchantAuditLogsQueryHandler(
|
||||
/// <returns>分页结果。</returns>
|
||||
public async Task<PagedResult<MerchantAuditLogDto>> Handle(GetMerchantAuditLogsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 获取租户上下文并查询日志
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var logs = await merchantRepository.GetAuditLogsAsync(request.MerchantId, tenantId, cancellationToken);
|
||||
// 1. 查询商户并解析租户
|
||||
var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken);
|
||||
if (merchant is null)
|
||||
{
|
||||
return new PagedResult<MerchantAuditLogDto>([], request.Page, request.PageSize, 0);
|
||||
}
|
||||
|
||||
// 2. (空行后) 查询日志
|
||||
var logs = await merchantRepository.GetAuditLogsAsync(request.MerchantId, merchant.TenantId, cancellationToken);
|
||||
var total = logs.Count;
|
||||
|
||||
// 2. 分页映射
|
||||
// 3. (空行后) 分页映射
|
||||
var paged = logs
|
||||
.Skip((request.Page - 1) * request.PageSize)
|
||||
.Take(request.PageSize)
|
||||
.Select(MerchantMapping.ToDto)
|
||||
.ToList();
|
||||
|
||||
// 3. 返回结果
|
||||
// 4. (空行后) 返回结果
|
||||
return new PagedResult<MerchantAuditLogDto>(paged, request.Page, request.PageSize, total);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,28 +2,26 @@ using MediatR;
|
||||
using TakeoutSaaS.Application.App.Merchants.Dto;
|
||||
using TakeoutSaaS.Application.App.Merchants.Queries;
|
||||
using TakeoutSaaS.Domain.Merchants.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 获取商户详情查询处理器。
|
||||
/// </summary>
|
||||
public sealed class GetMerchantByIdQueryHandler(IMerchantRepository merchantRepository, ITenantProvider tenantProvider)
|
||||
public sealed class GetMerchantByIdQueryHandler(IMerchantRepository merchantRepository)
|
||||
: IRequestHandler<GetMerchantByIdQuery, MerchantDto?>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<MerchantDto?> Handle(GetMerchantByIdQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 获取租户上下文
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, tenantId, cancellationToken);
|
||||
// 1. 查询商户(跨租户)
|
||||
var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken);
|
||||
if (merchant == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. 返回 DTO
|
||||
// 2. (空行后) 返回 DTO
|
||||
return new MerchantDto
|
||||
{
|
||||
Id = merchant.Id,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Merchants.Queries;
|
||||
using TakeoutSaaS.Domain.Merchants.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
|
||||
@@ -9,8 +8,7 @@ namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
/// 读取可选类目。
|
||||
/// </summary>
|
||||
public sealed class GetMerchantCategoriesQueryHandler(
|
||||
IMerchantCategoryRepository categoryRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
IMerchantCategoryRepository categoryRepository)
|
||||
: IRequestHandler<GetMerchantCategoriesQuery, IReadOnlyList<string>>
|
||||
{
|
||||
/// <summary>
|
||||
@@ -21,11 +19,10 @@ public sealed class GetMerchantCategoriesQueryHandler(
|
||||
/// <returns>类目名称集合。</returns>
|
||||
public async Task<IReadOnlyList<string>> Handle(GetMerchantCategoriesQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 获取租户上下文并读取类目
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var categories = await categoryRepository.ListAsync(tenantId, cancellationToken);
|
||||
// 1. 读取类目列表
|
||||
var categories = await categoryRepository.ListAsync(request.TenantId, cancellationToken);
|
||||
|
||||
// 2. 过滤启用类目并去重
|
||||
// 2. (空行后) 过滤启用类目并去重
|
||||
return categories
|
||||
.Where(x => x.IsActive)
|
||||
.Select(x => x.Name.Trim())
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Merchants.Dto;
|
||||
using TakeoutSaaS.Application.App.Merchants.Queries;
|
||||
using TakeoutSaaS.Application.Identity;
|
||||
using TakeoutSaaS.Application.Identity.Abstractions;
|
||||
using TakeoutSaaS.Domain.Merchants.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;
|
||||
|
||||
@@ -15,10 +11,7 @@ namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
/// 商户变更历史处理器。
|
||||
/// </summary>
|
||||
public sealed class GetMerchantChangeHistoryQueryHandler(
|
||||
IMerchantRepository merchantRepository,
|
||||
ITenantProvider tenantProvider,
|
||||
ICurrentUserAccessor currentUserAccessor,
|
||||
IAdminAuthService adminAuthService)
|
||||
IMerchantRepository merchantRepository)
|
||||
: IRequestHandler<GetMerchantChangeHistoryQuery, IReadOnlyList<MerchantChangeLogDto>>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
@@ -26,24 +19,14 @@ public sealed class GetMerchantChangeHistoryQueryHandler(
|
||||
GetMerchantChangeHistoryQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var currentTenantId = tenantProvider.GetCurrentTenantId();
|
||||
var operatorProfile = await adminAuthService.GetProfileAsync(currentUserAccessor.UserId, cancellationToken);
|
||||
var isSuperAdmin = IdentityUserAccess.IsSuperAdmin(operatorProfile);
|
||||
|
||||
var merchant = isSuperAdmin
|
||||
? await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken)
|
||||
: await merchantRepository.FindByIdAsync(request.MerchantId, currentTenantId, cancellationToken);
|
||||
|
||||
// 1. 查询商户(跨租户)
|
||||
var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken);
|
||||
if (merchant == null)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.NotFound, "商户不存在");
|
||||
}
|
||||
|
||||
if (!isSuperAdmin && merchant.TenantId != currentTenantId)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.Forbidden, "禁止访问其他租户的商户变更历史");
|
||||
}
|
||||
|
||||
// 2. (空行后) 查询变更历史
|
||||
var logs = await merchantRepository.GetChangeLogsAsync(merchant.Id, merchant.TenantId, request.FieldName, cancellationToken);
|
||||
return logs.Select(MerchantMapping.ToDto).ToList();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ using TakeoutSaaS.Application.App.Merchants.Queries;
|
||||
using TakeoutSaaS.Domain.Merchants.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
|
||||
@@ -12,8 +11,7 @@ namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
/// 查询合同列表。
|
||||
/// </summary>
|
||||
public sealed class GetMerchantContractsQueryHandler(
|
||||
IMerchantRepository merchantRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
IMerchantRepository merchantRepository)
|
||||
: IRequestHandler<GetMerchantContractsQuery, IReadOnlyList<MerchantContractDto>>
|
||||
{
|
||||
/// <summary>
|
||||
@@ -24,13 +22,12 @@ public sealed class GetMerchantContractsQueryHandler(
|
||||
/// <returns>合同 DTO 列表。</returns>
|
||||
public async Task<IReadOnlyList<MerchantContractDto>> Handle(GetMerchantContractsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 获取租户上下文并校验商户存在
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
_ = await merchantRepository.FindByIdAsync(request.MerchantId, tenantId, cancellationToken)
|
||||
// 1. 校验商户存在并解析租户
|
||||
var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken)
|
||||
?? throw new BusinessException(ErrorCodes.NotFound, "商户不存在");
|
||||
|
||||
// 2. 查询合同列表
|
||||
var contracts = await merchantRepository.GetContractsAsync(request.MerchantId, tenantId, cancellationToken);
|
||||
// 2. (空行后) 查询合同列表
|
||||
var contracts = await merchantRepository.GetContractsAsync(request.MerchantId, merchant.TenantId, cancellationToken);
|
||||
return MerchantMapping.ToContractDtos(contracts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Merchants.Dto;
|
||||
using TakeoutSaaS.Application.App.Merchants.Queries;
|
||||
using TakeoutSaaS.Application.Identity;
|
||||
using TakeoutSaaS.Application.Identity.Abstractions;
|
||||
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;
|
||||
|
||||
@@ -19,10 +15,7 @@ namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
public sealed class GetMerchantDetailQueryHandler(
|
||||
IMerchantRepository merchantRepository,
|
||||
IStoreRepository storeRepository,
|
||||
ITenantRepository tenantRepository,
|
||||
ITenantProvider tenantProvider,
|
||||
ICurrentUserAccessor currentUserAccessor,
|
||||
IAdminAuthService adminAuthService)
|
||||
ITenantRepository tenantRepository)
|
||||
: IRequestHandler<GetMerchantDetailQuery, MerchantDetailDto>
|
||||
{
|
||||
/// <summary>
|
||||
@@ -33,31 +26,19 @@ public sealed class GetMerchantDetailQueryHandler(
|
||||
/// <returns>商户详情 DTO。</returns>
|
||||
public async Task<MerchantDetailDto> Handle(GetMerchantDetailQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 获取权限与商户
|
||||
var currentTenantId = tenantProvider.GetCurrentTenantId();
|
||||
var operatorProfile = await adminAuthService.GetProfileAsync(currentUserAccessor.UserId, cancellationToken);
|
||||
var isSuperAdmin = IdentityUserAccess.IsSuperAdmin(operatorProfile);
|
||||
|
||||
var merchant = isSuperAdmin
|
||||
? await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken)
|
||||
: await merchantRepository.FindByIdAsync(request.MerchantId, currentTenantId, cancellationToken);
|
||||
|
||||
// 1. 查询商户(跨租户)
|
||||
var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken);
|
||||
if (merchant == null)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.NotFound, "商户不存在");
|
||||
}
|
||||
|
||||
if (!isSuperAdmin && merchant.TenantId != currentTenantId)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.Forbidden, "禁止访问其他租户的商户");
|
||||
}
|
||||
|
||||
// 2. 查询门店与租户信息
|
||||
// 2. (空行后) 查询门店与租户信息
|
||||
var stores = await storeRepository.GetByMerchantIdAsync(merchant.Id, merchant.TenantId, cancellationToken);
|
||||
var storeDtos = MerchantMapping.ToStoreDtos(stores);
|
||||
var tenant = await tenantRepository.FindByIdAsync(merchant.TenantId, cancellationToken);
|
||||
|
||||
// 3. 返回明细 DTO
|
||||
// 3. (空行后) 返回明细 DTO
|
||||
return MerchantMapping.ToDetailDto(merchant, tenant?.Name, storeDtos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ using TakeoutSaaS.Application.App.Merchants.Queries;
|
||||
using TakeoutSaaS.Domain.Merchants.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
|
||||
@@ -12,8 +11,7 @@ namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
/// 查询证照列表。
|
||||
/// </summary>
|
||||
public sealed class GetMerchantDocumentsQueryHandler(
|
||||
IMerchantRepository merchantRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
IMerchantRepository merchantRepository)
|
||||
: IRequestHandler<GetMerchantDocumentsQuery, IReadOnlyList<MerchantDocumentDto>>
|
||||
{
|
||||
/// <summary>
|
||||
@@ -24,13 +22,12 @@ public sealed class GetMerchantDocumentsQueryHandler(
|
||||
/// <returns>证照 DTO 列表。</returns>
|
||||
public async Task<IReadOnlyList<MerchantDocumentDto>> Handle(GetMerchantDocumentsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 获取租户上下文并校验商户存在
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
_ = await merchantRepository.FindByIdAsync(request.MerchantId, tenantId, cancellationToken)
|
||||
// 1. 校验商户存在并解析租户
|
||||
var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken)
|
||||
?? throw new BusinessException(ErrorCodes.NotFound, "商户不存在");
|
||||
|
||||
// 2. 查询证照列表
|
||||
var documents = await merchantRepository.GetDocumentsAsync(request.MerchantId, tenantId, cancellationToken);
|
||||
// 2. (空行后) 查询证照列表
|
||||
var documents = await merchantRepository.GetDocumentsAsync(request.MerchantId, merchant.TenantId, cancellationToken);
|
||||
return MerchantMapping.ToDocumentDtos(documents);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Merchants.Dto;
|
||||
using TakeoutSaaS.Application.App.Merchants.Queries;
|
||||
using TakeoutSaaS.Application.Identity;
|
||||
using TakeoutSaaS.Application.Identity.Abstractions;
|
||||
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.Results;
|
||||
using TakeoutSaaS.Shared.Abstractions.Security;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
|
||||
@@ -20,10 +14,7 @@ namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
public sealed class GetMerchantListQueryHandler(
|
||||
IMerchantRepository merchantRepository,
|
||||
IStoreRepository storeRepository,
|
||||
ITenantRepository tenantRepository,
|
||||
ITenantProvider tenantProvider,
|
||||
ICurrentUserAccessor currentUserAccessor,
|
||||
IAdminAuthService adminAuthService)
|
||||
ITenantRepository tenantRepository)
|
||||
: IRequestHandler<GetMerchantListQuery, PagedResult<MerchantListItemDto>>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
@@ -31,21 +22,9 @@ public sealed class GetMerchantListQueryHandler(
|
||||
GetMerchantListQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 校验跨租户访问权限
|
||||
var currentTenantId = tenantProvider.GetCurrentTenantId();
|
||||
var operatorProfile = await adminAuthService.GetProfileAsync(currentUserAccessor.UserId, cancellationToken);
|
||||
var isSuperAdmin = IdentityUserAccess.IsSuperAdmin(operatorProfile);
|
||||
|
||||
if (!isSuperAdmin && request.TenantId.HasValue && request.TenantId.Value != currentTenantId)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.Forbidden, "禁止跨租户查询商户");
|
||||
}
|
||||
|
||||
var effectiveTenantId = isSuperAdmin ? request.TenantId : currentTenantId;
|
||||
|
||||
// 2. 查询商户列表
|
||||
// 1. 查询商户列表
|
||||
var merchants = await merchantRepository.SearchAsync(
|
||||
effectiveTenantId,
|
||||
request.TenantId,
|
||||
request.Status,
|
||||
request.OperatingMode,
|
||||
request.Keyword,
|
||||
@@ -56,7 +35,7 @@ public sealed class GetMerchantListQueryHandler(
|
||||
return new PagedResult<MerchantListItemDto>(Array.Empty<MerchantListItemDto>(), request.Page, request.PageSize, 0);
|
||||
}
|
||||
|
||||
// 3. 排序 & 分页
|
||||
// 2. (空行后) 排序 & 分页
|
||||
var sorted = ApplySorting(merchants, request.SortBy, request.SortOrder);
|
||||
var total = sorted.Count;
|
||||
var paged = sorted
|
||||
@@ -69,16 +48,16 @@ public sealed class GetMerchantListQueryHandler(
|
||||
return new PagedResult<MerchantListItemDto>(Array.Empty<MerchantListItemDto>(), request.Page, request.PageSize, total);
|
||||
}
|
||||
|
||||
// 4. 批量查询租户名称
|
||||
// 3. (空行后) 批量查询租户名称
|
||||
var tenantIds = paged.Select(x => x.TenantId).Distinct().ToArray();
|
||||
var tenants = await tenantRepository.FindByIdsAsync(tenantIds, cancellationToken);
|
||||
var tenantLookup = tenants.ToDictionary(x => x.Id, x => x.Name);
|
||||
|
||||
// 5. 批量查询门店数量
|
||||
// 4. (空行后) 批量查询门店数量
|
||||
var merchantIds = paged.Select(x => x.Id).ToArray();
|
||||
var storeCounts = await storeRepository.GetStoreCountsAsync(effectiveTenantId, merchantIds, cancellationToken);
|
||||
var storeCounts = await storeRepository.GetStoreCountsAsync(request.TenantId, merchantIds, cancellationToken);
|
||||
|
||||
// 6. 组装 DTO
|
||||
// 5. (空行后) 组装 DTO
|
||||
var items = paged.Select(merchant =>
|
||||
{
|
||||
var tenantName = tenantLookup.TryGetValue(merchant.TenantId, out var name) ? name : null;
|
||||
|
||||
@@ -2,7 +2,6 @@ using MediatR;
|
||||
using TakeoutSaaS.Application.App.Merchants.Dto;
|
||||
using TakeoutSaaS.Application.App.Merchants.Queries;
|
||||
using TakeoutSaaS.Domain.Merchants.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
|
||||
@@ -10,8 +9,7 @@ namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
/// 列出类目。
|
||||
/// </summary>
|
||||
public sealed class ListMerchantCategoriesQueryHandler(
|
||||
IMerchantCategoryRepository categoryRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
IMerchantCategoryRepository categoryRepository)
|
||||
: IRequestHandler<ListMerchantCategoriesQuery, IReadOnlyList<MerchantCategoryDto>>
|
||||
{
|
||||
/// <summary>
|
||||
@@ -22,11 +20,10 @@ public sealed class ListMerchantCategoriesQueryHandler(
|
||||
/// <returns>类目 DTO 列表。</returns>
|
||||
public async Task<IReadOnlyList<MerchantCategoryDto>> Handle(ListMerchantCategoriesQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 获取租户上下文
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var categories = await categoryRepository.ListAsync(tenantId, cancellationToken);
|
||||
// 1. 查询类目列表
|
||||
var categories = await categoryRepository.ListAsync(request.TenantId, cancellationToken);
|
||||
|
||||
// 2. 映射 DTO
|
||||
// 2. (空行后) 映射 DTO
|
||||
return MerchantMapping.ToCategoryDtos(categories);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using TakeoutSaaS.Application.App.Merchants.Commands;
|
||||
using TakeoutSaaS.Domain.Merchants.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
|
||||
@@ -11,8 +10,7 @@ namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
/// 类目排序处理器。
|
||||
/// </summary>
|
||||
public sealed class ReorderMerchantCategoriesCommandHandler(
|
||||
IMerchantCategoryRepository categoryRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
IMerchantCategoryRepository categoryRepository)
|
||||
: IRequestHandler<ReorderMerchantCategoriesCommand, bool>
|
||||
{
|
||||
/// <summary>
|
||||
@@ -23,12 +21,11 @@ public sealed class ReorderMerchantCategoriesCommandHandler(
|
||||
/// <returns>执行结果。</returns>
|
||||
public async Task<bool> Handle(ReorderMerchantCategoriesCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 获取租户并查询类目
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var categories = await categoryRepository.ListAsync(tenantId, cancellationToken);
|
||||
// 1. 查询类目列表
|
||||
var categories = await categoryRepository.ListAsync(request.TenantId, cancellationToken);
|
||||
var map = categories.ToDictionary(x => x.Id);
|
||||
|
||||
// 2. 更新排序
|
||||
// 2. (空行后) 更新排序
|
||||
foreach (var item in request.Items)
|
||||
{
|
||||
if (!map.TryGetValue(item.CategoryId, out var category))
|
||||
@@ -39,7 +36,7 @@ public sealed class ReorderMerchantCategoriesCommandHandler(
|
||||
category.DisplayOrder = item.DisplayOrder;
|
||||
}
|
||||
|
||||
// 3. 持久化
|
||||
// 3. (空行后) 持久化
|
||||
await categoryRepository.UpdateRangeAsync(map.Values, cancellationToken);
|
||||
await categoryRepository.SaveChangesAsync(cancellationToken);
|
||||
return true;
|
||||
|
||||
@@ -7,7 +7,6 @@ using TakeoutSaaS.Domain.Merchants.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;
|
||||
|
||||
@@ -16,7 +15,6 @@ namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
/// </summary>
|
||||
public sealed class ReviewMerchantDocumentCommandHandler(
|
||||
IMerchantRepository merchantRepository,
|
||||
ITenantProvider tenantProvider,
|
||||
ICurrentUserAccessor currentUserAccessor)
|
||||
: IRequestHandler<ReviewMerchantDocumentCommand, MerchantDocumentDto>
|
||||
{
|
||||
@@ -28,23 +26,27 @@ public sealed class ReviewMerchantDocumentCommandHandler(
|
||||
/// <returns>证照 DTO。</returns>
|
||||
public async Task<MerchantDocumentDto> Handle(ReviewMerchantDocumentCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 读取证照
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
// 1. 查询商户并解析租户
|
||||
var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken)
|
||||
?? throw new BusinessException(ErrorCodes.NotFound, "商户不存在");
|
||||
var tenantId = merchant.TenantId;
|
||||
|
||||
// 2. (空行后) 读取证照
|
||||
var document = await merchantRepository.FindDocumentByIdAsync(request.MerchantId, tenantId, request.DocumentId, cancellationToken)
|
||||
?? throw new BusinessException(ErrorCodes.NotFound, "证照不存在");
|
||||
|
||||
// 2. 若状态无变化且备注相同,直接返回
|
||||
// 3. (空行后) 若状态无变化且备注相同,直接返回
|
||||
var targetStatus = request.Approve ? MerchantDocumentStatus.Approved : MerchantDocumentStatus.Rejected;
|
||||
if (document.Status == targetStatus && document.Remarks == request.Remarks)
|
||||
{
|
||||
return MerchantMapping.ToDto(document);
|
||||
}
|
||||
|
||||
// 3. 更新状态
|
||||
// 4. (空行后) 更新状态
|
||||
document.Status = targetStatus;
|
||||
document.Remarks = request.Remarks;
|
||||
|
||||
// 4. 持久化与审计
|
||||
// 5. (空行后) 持久化与审计
|
||||
await merchantRepository.UpdateDocumentAsync(document, cancellationToken);
|
||||
await merchantRepository.AddAuditLogAsync(new MerchantAuditLog
|
||||
{
|
||||
@@ -58,7 +60,7 @@ public sealed class ReviewMerchantDocumentCommandHandler(
|
||||
}, cancellationToken);
|
||||
await merchantRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 5. 返回 DTO
|
||||
// 6. (空行后) 返回 DTO
|
||||
return MerchantMapping.ToDto(document);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ using TakeoutSaaS.Application.App.Merchants.Dto;
|
||||
using TakeoutSaaS.Application.App.Merchants.Queries;
|
||||
using TakeoutSaaS.Domain.Merchants.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Results;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
|
||||
@@ -11,25 +10,28 @@ namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
/// 商户列表查询处理器。
|
||||
/// </summary>
|
||||
public sealed class SearchMerchantsQueryHandler(
|
||||
IMerchantRepository merchantRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
IMerchantRepository merchantRepository)
|
||||
: IRequestHandler<SearchMerchantsQuery, PagedResult<MerchantDto>>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<PagedResult<MerchantDto>> Handle(SearchMerchantsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 获取租户并查询商户
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var merchants = await merchantRepository.SearchAsync(tenantId, request.Status, cancellationToken);
|
||||
// 1. 查询商户列表(可选租户过滤)
|
||||
var merchants = await merchantRepository.SearchAsync(
|
||||
request.TenantId,
|
||||
request.Status,
|
||||
operatingMode: null,
|
||||
keyword: null,
|
||||
cancellationToken);
|
||||
|
||||
// 2. 排序与分页
|
||||
// 2. (空行后) 排序与分页
|
||||
var sorted = ApplySorting(merchants, request.SortBy, request.SortDescending);
|
||||
var paged = sorted
|
||||
.Skip((request.Page - 1) * request.PageSize)
|
||||
.Take(request.PageSize)
|
||||
.ToList();
|
||||
|
||||
// 3. 映射 DTO
|
||||
// 3. (空行后) 映射 DTO
|
||||
var items = paged.Select(merchant => new MerchantDto
|
||||
{
|
||||
Id = merchant.Id,
|
||||
@@ -45,7 +47,7 @@ public sealed class SearchMerchantsQueryHandler(
|
||||
CreatedAt = merchant.CreatedAt
|
||||
}).ToList();
|
||||
|
||||
// 4. 返回分页结果
|
||||
// 4. (空行后) 返回分页结果
|
||||
return new PagedResult<MerchantDto>(items, request.Page, request.PageSize, merchants.Count);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@ using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TakeoutSaaS.Application.App.Merchants.Commands;
|
||||
using TakeoutSaaS.Application.App.Merchants.Dto;
|
||||
using TakeoutSaaS.Application.Identity;
|
||||
using TakeoutSaaS.Application.Identity.Abstractions;
|
||||
using TakeoutSaaS.Domain.Merchants.Entities;
|
||||
using TakeoutSaaS.Domain.Merchants.Enums;
|
||||
using TakeoutSaaS.Domain.Merchants.Repositories;
|
||||
@@ -12,7 +10,6 @@ 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;
|
||||
|
||||
@@ -23,9 +20,7 @@ public sealed class UpdateMerchantCommandHandler(
|
||||
IMerchantRepository merchantRepository,
|
||||
IStoreRepository storeRepository,
|
||||
ITenantRepository tenantRepository,
|
||||
ITenantProvider tenantProvider,
|
||||
ICurrentUserAccessor currentUserAccessor,
|
||||
IAdminAuthService adminAuthService,
|
||||
ILogger<UpdateMerchantCommandHandler> logger)
|
||||
: IRequestHandler<UpdateMerchantCommand, UpdateMerchantResultDto?>
|
||||
{
|
||||
@@ -37,27 +32,15 @@ public sealed class UpdateMerchantCommandHandler(
|
||||
throw new BusinessException(ErrorCodes.ValidationFailed, "RowVersion 不能为空");
|
||||
}
|
||||
|
||||
// 1. 获取操作者权限
|
||||
var currentTenantId = tenantProvider.GetCurrentTenantId();
|
||||
var operatorProfile = await adminAuthService.GetProfileAsync(currentUserAccessor.UserId, cancellationToken);
|
||||
var isSuperAdmin = IdentityUserAccess.IsSuperAdmin(operatorProfile);
|
||||
|
||||
// 2. 读取商户信息
|
||||
var merchant = isSuperAdmin
|
||||
? await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken)
|
||||
: await merchantRepository.FindByIdAsync(request.MerchantId, currentTenantId, cancellationToken);
|
||||
// 1. 读取商户信息(跨租户)
|
||||
var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken);
|
||||
|
||||
if (merchant == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isSuperAdmin && merchant.TenantId != currentTenantId)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3. 规范化输入
|
||||
// 2. (空行后) 规范化输入
|
||||
var name = NormalizeRequired(request.Name, "商户名称");
|
||||
var contactPhone = NormalizeRequired(request.ContactPhone, "联系电话");
|
||||
var licenseNumber = NormalizeOptional(request.LicenseNumber);
|
||||
@@ -78,7 +61,7 @@ public sealed class UpdateMerchantCommandHandler(
|
||||
TrackChange("contactPhone", merchant.ContactPhone, contactPhone, isCritical: false);
|
||||
TrackChange("contactEmail", merchant.ContactEmail, contactEmail, isCritical: false);
|
||||
|
||||
// 4. 写入字段
|
||||
// 3. (空行后) 写入字段
|
||||
merchant.BrandName = name;
|
||||
merchant.BusinessLicenseNumber = licenseNumber;
|
||||
merchant.LegalPerson = legalRepresentative;
|
||||
@@ -103,7 +86,7 @@ public sealed class UpdateMerchantCommandHandler(
|
||||
merchant.FrozenAt = null;
|
||||
}
|
||||
|
||||
// 5. 持久化日志与数据
|
||||
// 4. (空行后) 持久化日志与数据
|
||||
await merchantRepository.UpdateMerchantAsync(merchant, cancellationToken);
|
||||
foreach (var log in changes)
|
||||
{
|
||||
@@ -135,7 +118,7 @@ public sealed class UpdateMerchantCommandHandler(
|
||||
|
||||
logger.LogInformation("更新商户 {MerchantId} - {Name}", merchant.Id, merchant.BrandName);
|
||||
|
||||
// 6. 返回更新结果
|
||||
// 5. (空行后) 返回更新结果
|
||||
var stores = await storeRepository.GetByMerchantIdAsync(merchant.Id, merchant.TenantId, cancellationToken);
|
||||
var tenant = await tenantRepository.FindByIdAsync(merchant.TenantId, cancellationToken);
|
||||
var detail = MerchantMapping.ToDetailDto(merchant, tenant?.Name, MerchantMapping.ToStoreDtos(stores));
|
||||
|
||||
@@ -7,7 +7,6 @@ using TakeoutSaaS.Domain.Merchants.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;
|
||||
|
||||
@@ -16,7 +15,6 @@ namespace TakeoutSaaS.Application.App.Merchants.Handlers;
|
||||
/// </summary>
|
||||
public sealed class UpdateMerchantContractStatusCommandHandler(
|
||||
IMerchantRepository merchantRepository,
|
||||
ITenantProvider tenantProvider,
|
||||
ICurrentUserAccessor currentUserAccessor)
|
||||
: IRequestHandler<UpdateMerchantContractStatusCommand, MerchantContractDto>
|
||||
{
|
||||
@@ -28,12 +26,16 @@ public sealed class UpdateMerchantContractStatusCommandHandler(
|
||||
/// <returns>合同 DTO。</returns>
|
||||
public async Task<MerchantContractDto> Handle(UpdateMerchantContractStatusCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 查询合同
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
// 1. 查询商户并解析租户
|
||||
var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, cancellationToken)
|
||||
?? throw new BusinessException(ErrorCodes.NotFound, "商户不存在");
|
||||
var tenantId = merchant.TenantId;
|
||||
|
||||
// 2. (空行后) 查询合同
|
||||
var contract = await merchantRepository.FindContractByIdAsync(request.MerchantId, tenantId, request.ContractId, cancellationToken)
|
||||
?? throw new BusinessException(ErrorCodes.NotFound, "合同不存在");
|
||||
|
||||
// 2. 更新状态
|
||||
// 3. (空行后) 更新状态
|
||||
if (request.Status == ContractStatus.Active)
|
||||
{
|
||||
contract.Status = ContractStatus.Active;
|
||||
@@ -50,7 +52,7 @@ public sealed class UpdateMerchantContractStatusCommandHandler(
|
||||
contract.Status = request.Status;
|
||||
}
|
||||
|
||||
// 3. 持久化与审计
|
||||
// 4. (空行后) 持久化与审计
|
||||
await merchantRepository.UpdateContractAsync(contract, cancellationToken);
|
||||
await merchantRepository.AddAuditLogAsync(new MerchantAuditLog
|
||||
{
|
||||
|
||||
@@ -5,4 +5,4 @@ namespace TakeoutSaaS.Application.App.Merchants.Queries;
|
||||
/// <summary>
|
||||
/// 获取商户可选类目。
|
||||
/// </summary>
|
||||
public sealed record GetMerchantCategoriesQuery() : IRequest<IReadOnlyList<string>>;
|
||||
public sealed record GetMerchantCategoriesQuery(long TenantId) : IRequest<IReadOnlyList<string>>;
|
||||
|
||||
@@ -6,4 +6,10 @@ namespace TakeoutSaaS.Application.App.Merchants.Queries;
|
||||
/// <summary>
|
||||
/// 管理端获取完整类目列表。
|
||||
/// </summary>
|
||||
public sealed record ListMerchantCategoriesQuery() : IRequest<IReadOnlyList<MerchantCategoryDto>>;
|
||||
public sealed record ListMerchantCategoriesQuery : IRequest<IReadOnlyList<MerchantCategoryDto>>
|
||||
{
|
||||
/// <summary>
|
||||
/// 租户 ID。
|
||||
/// </summary>
|
||||
public long TenantId { get; init; }
|
||||
}
|
||||
|
||||
@@ -10,6 +10,11 @@ namespace TakeoutSaaS.Application.App.Merchants.Queries;
|
||||
/// </summary>
|
||||
public sealed class SearchMerchantsQuery : IRequest<PagedResult<MerchantDto>>
|
||||
{
|
||||
/// <summary>
|
||||
/// 租户过滤(为空表示跨租户查询)。
|
||||
/// </summary>
|
||||
public long? TenantId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 按状态过滤。
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user