From 63b05da39a15530f70364059e2ce794f4d2fd7a7 Mon Sep 17 00:00:00 2001 From: MSuMshk <2039814060@qq.com> Date: Thu, 29 Jan 2026 12:14:43 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=AE=A2=E5=8D=95=E5=95=86=E5=93=81?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E6=94=AF=E6=8C=81tenantId=E5=8F=AF=E9=80=89?= =?UTF-8?q?=E8=BF=87=E6=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/OrdersController.cs | 2 + .../Controllers/ProductsController.cs | 2 + .../Handlers/GetOrderByIdQueryHandler.cs | 16 +++--- .../Handlers/SearchOrdersQueryHandler.cs | 19 +++---- .../App/Orders/Queries/SearchOrdersQuery.cs | 5 ++ .../Handlers/GetProductByIdQueryHandler.cs | 13 ++--- .../Handlers/GetProductDetailQueryHandler.cs | 14 +++-- .../Handlers/SearchProductsQueryHandler.cs | 18 ++++--- .../Products/Queries/SearchProductsQuery.cs | 5 ++ .../Orders/Repositories/IOrderRepository.cs | 18 +++++++ .../Repositories/IProductRepository.cs | 10 ++++ .../App/Repositories/EfOrderRepository.cs | 39 ++++++++++++++ .../App/Repositories/EfProductRepository.cs | 51 +++++++++++++++++++ 13 files changed, 166 insertions(+), 46 deletions(-) diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/OrdersController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/OrdersController.cs index d190e96..cf69c2f 100644 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/OrdersController.cs +++ b/src/Api/TakeoutSaaS.AdminApi/Controllers/OrdersController.cs @@ -45,6 +45,7 @@ public sealed class OrdersController(IMediator mediator) : BaseApiController [PermissionAuthorize("order:read")] [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] public async Task>> List( + [FromQuery] long? tenantId, [FromQuery] long? storeId, [FromQuery] OrderStatus? status, [FromQuery] PaymentStatus? paymentStatus, @@ -58,6 +59,7 @@ public sealed class OrdersController(IMediator mediator) : BaseApiController // 1. 组装查询参数并执行查询 var result = await mediator.Send(new SearchOrdersQuery { + TenantId = tenantId, StoreId = storeId, Status = status, PaymentStatus = paymentStatus, diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/ProductsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/ProductsController.cs index 0dfc911..a1e320c 100644 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/ProductsController.cs +++ b/src/Api/TakeoutSaaS.AdminApi/Controllers/ProductsController.cs @@ -44,6 +44,7 @@ public sealed class ProductsController(IMediator mediator) : BaseApiController [PermissionAuthorize("product:read")] [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] public async Task>> List( + [FromQuery] long? tenantId, [FromQuery] long? storeId, [FromQuery] long? categoryId, [FromQuery] ProductStatus? status, @@ -56,6 +57,7 @@ public sealed class ProductsController(IMediator mediator) : BaseApiController // 1. 组装查询参数并执行查询 var result = await mediator.Send(new SearchProductsQuery { + TenantId = tenantId, StoreId = storeId, CategoryId = categoryId, Status = status, diff --git a/src/Application/TakeoutSaaS.Application/App/Orders/Handlers/GetOrderByIdQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Orders/Handlers/GetOrderByIdQueryHandler.cs index 3a45377..71f4a3f 100644 --- a/src/Application/TakeoutSaaS.Application/App/Orders/Handlers/GetOrderByIdQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Orders/Handlers/GetOrderByIdQueryHandler.cs @@ -3,7 +3,6 @@ using TakeoutSaaS.Application.App.Orders.Dto; using TakeoutSaaS.Application.App.Orders.Queries; using TakeoutSaaS.Domain.Orders.Entities; using TakeoutSaaS.Domain.Orders.Repositories; -using TakeoutSaaS.Shared.Abstractions.Tenancy; namespace TakeoutSaaS.Application.App.Orders.Handlers; @@ -11,29 +10,26 @@ namespace TakeoutSaaS.Application.App.Orders.Handlers; /// 订单详情查询处理器。 /// public sealed class GetOrderByIdQueryHandler( - IOrderRepository orderRepository, - ITenantProvider tenantProvider) + IOrderRepository orderRepository) : IRequestHandler { /// public async Task Handle(GetOrderByIdQuery request, CancellationToken cancellationToken) { - // 1. 获取当前租户 - var tenantId = tenantProvider.GetCurrentTenantId(); - - // 2. 查询订单主体 - var order = await orderRepository.FindByIdAsync(request.OrderId, tenantId, cancellationToken); + // 1. 查询订单主体(跨租户) + var order = await orderRepository.FindByIdAsync(request.OrderId, cancellationToken); if (order == null) { return null; } - // 3. 查询关联明细 + // 2. (空行后) 查询关联明细(按订单租户) + var tenantId = order.TenantId; var items = await orderRepository.GetItemsAsync(order.Id, tenantId, cancellationToken); var histories = await orderRepository.GetStatusHistoryAsync(order.Id, tenantId, cancellationToken); var refunds = await orderRepository.GetRefundsAsync(order.Id, tenantId, cancellationToken); - // 4. 映射并返回 + // 3. (空行后) 映射并返回 return MapToDto(order, items, histories, refunds); } diff --git a/src/Application/TakeoutSaaS.Application/App/Orders/Handlers/SearchOrdersQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Orders/Handlers/SearchOrdersQueryHandler.cs index 90844c1..e3b0d26 100644 --- a/src/Application/TakeoutSaaS.Application/App/Orders/Handlers/SearchOrdersQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Orders/Handlers/SearchOrdersQueryHandler.cs @@ -3,7 +3,6 @@ using TakeoutSaaS.Application.App.Orders.Dto; using TakeoutSaaS.Application.App.Orders.Queries; using TakeoutSaaS.Domain.Orders.Repositories; using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Abstractions.Tenancy; namespace TakeoutSaaS.Application.App.Orders.Handlers; @@ -11,24 +10,22 @@ namespace TakeoutSaaS.Application.App.Orders.Handlers; /// 订单列表查询处理器。 /// public sealed class SearchOrdersQueryHandler( - IOrderRepository orderRepository, - ITenantProvider tenantProvider) + IOrderRepository orderRepository) : IRequestHandler> { /// public async Task> Handle(SearchOrdersQuery request, CancellationToken cancellationToken) { - // 1. 获取当前租户并查询订单 - var tenantId = tenantProvider.GetCurrentTenantId(); - var orders = await orderRepository.SearchAsync(tenantId, request.Status, request.PaymentStatus, cancellationToken); + // 1. 查询订单(可选租户过滤) + var orders = await orderRepository.SearchAsync(request.TenantId, request.Status, request.PaymentStatus, cancellationToken); - // 2. 可选过滤:门店 + // 2. (空行后) 可选过滤:门店 if (request.StoreId.HasValue) { orders = orders.Where(x => x.StoreId == request.StoreId.Value).ToList(); } - // 3. 可选过滤:订单号模糊 + // 3. (空行后) 可选过滤:订单号模糊 if (!string.IsNullOrWhiteSpace(request.OrderNo)) { var orderNo = request.OrderNo.Trim(); @@ -37,14 +34,14 @@ public sealed class SearchOrdersQueryHandler( .ToList(); } - // 4. 排序与分页 + // 4. (空行后) 排序与分页 var sorted = ApplySorting(orders, request.SortBy, request.SortDescending); var paged = sorted .Skip((request.Page - 1) * request.PageSize) .Take(request.PageSize) .ToList(); - // 5. 映射 DTO + // 5. (空行后) 映射 DTO var items = paged.Select(order => new OrderDto { Id = order.Id, @@ -72,7 +69,7 @@ public sealed class SearchOrdersQueryHandler( CreatedAt = order.CreatedAt }).ToList(); - // 6. 返回分页结果 + // 6. (空行后) 返回分页结果 return new PagedResult(items, request.Page, request.PageSize, orders.Count); } diff --git a/src/Application/TakeoutSaaS.Application/App/Orders/Queries/SearchOrdersQuery.cs b/src/Application/TakeoutSaaS.Application/App/Orders/Queries/SearchOrdersQuery.cs index 74c6753..be88e28 100644 --- a/src/Application/TakeoutSaaS.Application/App/Orders/Queries/SearchOrdersQuery.cs +++ b/src/Application/TakeoutSaaS.Application/App/Orders/Queries/SearchOrdersQuery.cs @@ -11,6 +11,11 @@ namespace TakeoutSaaS.Application.App.Orders.Queries; /// public sealed class SearchOrdersQuery : IRequest> { + /// + /// 租户 ID(可选,空表示跨租户查询)。 + /// + public long? TenantId { get; init; } + /// /// 门店 ID(可选)。 /// diff --git a/src/Application/TakeoutSaaS.Application/App/Products/Handlers/GetProductByIdQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Products/Handlers/GetProductByIdQueryHandler.cs index bed5199..5652157 100644 --- a/src/Application/TakeoutSaaS.Application/App/Products/Handlers/GetProductByIdQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Products/Handlers/GetProductByIdQueryHandler.cs @@ -1,9 +1,7 @@ using MediatR; using TakeoutSaaS.Application.App.Products.Dto; using TakeoutSaaS.Application.App.Products.Queries; -using TakeoutSaaS.Domain.Products.Entities; using TakeoutSaaS.Domain.Products.Repositories; -using TakeoutSaaS.Shared.Abstractions.Tenancy; namespace TakeoutSaaS.Application.App.Products.Handlers; @@ -11,18 +9,15 @@ namespace TakeoutSaaS.Application.App.Products.Handlers; /// 商品详情查询处理器。 /// public sealed class GetProductByIdQueryHandler( - IProductRepository productRepository, - ITenantProvider tenantProvider) + IProductRepository productRepository) : IRequestHandler { - private readonly IProductRepository _productRepository = productRepository; - private readonly ITenantProvider _tenantProvider = tenantProvider; - /// public async Task Handle(GetProductByIdQuery request, CancellationToken cancellationToken) { - var tenantId = _tenantProvider.GetCurrentTenantId(); - var product = await _productRepository.FindByIdAsync(request.ProductId, tenantId, cancellationToken); + // 1. 查询商品(跨租户) + var product = await productRepository.FindByIdAsync(request.ProductId, cancellationToken); + // 2. (空行后) 映射并返回 return product == null ? null : ProductMapping.ToDto(product); } } diff --git a/src/Application/TakeoutSaaS.Application/App/Products/Handlers/GetProductDetailQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Products/Handlers/GetProductDetailQueryHandler.cs index 828f1c8..9e9e573 100644 --- a/src/Application/TakeoutSaaS.Application/App/Products/Handlers/GetProductDetailQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Products/Handlers/GetProductDetailQueryHandler.cs @@ -2,7 +2,6 @@ using MediatR; using TakeoutSaaS.Application.App.Products.Dto; using TakeoutSaaS.Application.App.Products.Queries; using TakeoutSaaS.Domain.Products.Repositories; -using TakeoutSaaS.Shared.Abstractions.Tenancy; namespace TakeoutSaaS.Application.App.Products.Handlers; @@ -10,22 +9,21 @@ namespace TakeoutSaaS.Application.App.Products.Handlers; /// 商品全量详情查询处理器。 /// public sealed class GetProductDetailQueryHandler( - IProductRepository productRepository, - ITenantProvider tenantProvider) + IProductRepository productRepository) : IRequestHandler { /// public async Task Handle(GetProductDetailQuery request, CancellationToken cancellationToken) { - // 1. 读取 SPU - var tenantId = tenantProvider.GetCurrentTenantId(); - var product = await productRepository.FindByIdAsync(request.ProductId, tenantId, cancellationToken); + // 1. 读取 SPU(跨租户) + var product = await productRepository.FindByIdAsync(request.ProductId, cancellationToken); if (product is null) { return null; } - // 2. 查询子项 + // 2. (空行后) 读取租户并查询子项 + var tenantId = product.TenantId; var skusTask = productRepository.GetSkusAsync(product.Id, tenantId, cancellationToken); var attrGroupsTask = productRepository.GetAttributeGroupsAsync(product.Id, tenantId, cancellationToken); var attrOptionsTask = productRepository.GetAttributeOptionsAsync(product.Id, tenantId, cancellationToken); @@ -36,7 +34,7 @@ public sealed class GetProductDetailQueryHandler( await Task.WhenAll(skusTask, attrGroupsTask, attrOptionsTask, addonGroupsTask, addonOptionsTask, mediaTask, pricingTask); - // 3. 组装 DTO + // 3. (空行后) 组装 DTO var skus = await skusTask; var attrGroups = await attrGroupsTask; var attrOptions = (await attrOptionsTask).ToLookup(x => x.AttributeGroupId); diff --git a/src/Application/TakeoutSaaS.Application/App/Products/Handlers/SearchProductsQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Products/Handlers/SearchProductsQueryHandler.cs index 660a35b..41442dd 100644 --- a/src/Application/TakeoutSaaS.Application/App/Products/Handlers/SearchProductsQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Products/Handlers/SearchProductsQueryHandler.cs @@ -3,7 +3,6 @@ using TakeoutSaaS.Application.App.Products.Dto; using TakeoutSaaS.Application.App.Products.Queries; using TakeoutSaaS.Domain.Products.Repositories; using TakeoutSaaS.Shared.Abstractions.Results; -using TakeoutSaaS.Shared.Abstractions.Tenancy; namespace TakeoutSaaS.Application.App.Products.Handlers; @@ -11,25 +10,28 @@ namespace TakeoutSaaS.Application.App.Products.Handlers; /// 商品列表查询处理器。 /// public sealed class SearchProductsQueryHandler( - IProductRepository productRepository, - ITenantProvider tenantProvider) + IProductRepository productRepository) : IRequestHandler> { - private readonly IProductRepository _productRepository = productRepository; - private readonly ITenantProvider _tenantProvider = tenantProvider; - /// public async Task> Handle(SearchProductsQuery request, CancellationToken cancellationToken) { - var tenantId = _tenantProvider.GetCurrentTenantId(); - var products = await _productRepository.SearchAsync(tenantId, request.StoreId, request.CategoryId, request.Status, cancellationToken); + // 1. 查询商品列表(可选租户过滤) + var products = await productRepository.SearchAsync( + request.TenantId, + request.StoreId, + request.CategoryId, + request.Status, + cancellationToken); + // 2. (空行后) 排序与分页 var sorted = ApplySorting(products, request.SortBy, request.SortDescending); var paged = sorted .Skip((request.Page - 1) * request.PageSize) .Take(request.PageSize) .ToList(); + // 3. (空行后) 映射 DTO 并返回分页结果 var items = paged.Select(MapToDto).ToList(); return new PagedResult(items, request.Page, request.PageSize, products.Count); } diff --git a/src/Application/TakeoutSaaS.Application/App/Products/Queries/SearchProductsQuery.cs b/src/Application/TakeoutSaaS.Application/App/Products/Queries/SearchProductsQuery.cs index b1d4b31..0ef35f9 100644 --- a/src/Application/TakeoutSaaS.Application/App/Products/Queries/SearchProductsQuery.cs +++ b/src/Application/TakeoutSaaS.Application/App/Products/Queries/SearchProductsQuery.cs @@ -10,6 +10,11 @@ namespace TakeoutSaaS.Application.App.Products.Queries; /// public sealed class SearchProductsQuery : IRequest> { + /// + /// 租户 ID(可选,空表示跨租户查询)。 + /// + public long? TenantId { get; init; } + /// /// 门店 ID(可选)。 /// diff --git a/src/Domain/TakeoutSaaS.Domain/Orders/Repositories/IOrderRepository.cs b/src/Domain/TakeoutSaaS.Domain/Orders/Repositories/IOrderRepository.cs index 96a3bd6..8192a74 100644 --- a/src/Domain/TakeoutSaaS.Domain/Orders/Repositories/IOrderRepository.cs +++ b/src/Domain/TakeoutSaaS.Domain/Orders/Repositories/IOrderRepository.cs @@ -18,6 +18,14 @@ public interface IOrderRepository /// 订单实体或 null。 Task FindByIdAsync(long orderId, long tenantId, CancellationToken cancellationToken = default); + /// + /// 依据标识获取订单(跨租户)。 + /// + /// 订单 ID。 + /// 取消标记。 + /// 订单实体或 null。 + Task FindByIdAsync(long orderId, CancellationToken cancellationToken = default); + /// /// 依据订单号获取订单。 /// @@ -37,6 +45,16 @@ public interface IOrderRepository /// 订单集合。 Task> SearchAsync(long tenantId, OrderStatus? status, PaymentStatus? paymentStatus, CancellationToken cancellationToken = default); + /// + /// 按状态筛选订单列表(可选租户过滤)。 + /// + /// 租户 ID(可选,空表示跨租户查询)。 + /// 订单状态。 + /// 支付状态。 + /// 取消标记。 + /// 订单集合。 + Task> SearchAsync(long? tenantId, OrderStatus? status, PaymentStatus? paymentStatus, CancellationToken cancellationToken = default); + /// /// 获取订单明细行。 /// diff --git a/src/Domain/TakeoutSaaS.Domain/Products/Repositories/IProductRepository.cs b/src/Domain/TakeoutSaaS.Domain/Products/Repositories/IProductRepository.cs index 5b4563a..ad22d55 100644 --- a/src/Domain/TakeoutSaaS.Domain/Products/Repositories/IProductRepository.cs +++ b/src/Domain/TakeoutSaaS.Domain/Products/Repositories/IProductRepository.cs @@ -13,11 +13,21 @@ public interface IProductRepository /// Task FindByIdAsync(long productId, long tenantId, CancellationToken cancellationToken = default); + /// + /// 依据标识获取商品(跨租户)。 + /// + Task FindByIdAsync(long productId, CancellationToken cancellationToken = default); + /// /// 按分类与状态筛选商品列表。 /// Task> SearchAsync(long tenantId, long? storeId, long? categoryId, ProductStatus? status, CancellationToken cancellationToken = default, DateTime? updatedAfter = null); + /// + /// 按分类与状态筛选商品列表(可选租户过滤)。 + /// + Task> SearchAsync(long? tenantId, long? storeId, long? categoryId, ProductStatus? status, CancellationToken cancellationToken = default, DateTime? updatedAfter = null); + /// /// 获取租户下的商品分类。 /// diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfOrderRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfOrderRepository.cs index 0b117d7..3419bd6 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfOrderRepository.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfOrderRepository.cs @@ -24,6 +24,16 @@ public sealed class EfOrderRepository(TakeoutAdminDbContext context) : IOrderRep .FirstOrDefaultAsync(cancellationToken); } + /// + public Task FindByIdAsync(long orderId, CancellationToken cancellationToken = default) + { + // 1. 按主键查询(跨租户) + return context.Orders + .AsNoTracking() + .Where(x => x.Id == orderId) + .FirstOrDefaultAsync(cancellationToken); + } + /// public Task FindByOrderNoAsync(string orderNo, long tenantId, CancellationToken cancellationToken = default) { @@ -57,6 +67,35 @@ public sealed class EfOrderRepository(TakeoutAdminDbContext context) : IOrderRep return orders; } + /// + public async Task> SearchAsync(long? tenantId, OrderStatus? status, PaymentStatus? paymentStatus, CancellationToken cancellationToken = default) + { + // 1. 构建查询(可选租户过滤) + var query = context.Orders.AsNoTracking(); + if (tenantId.HasValue) + { + query = query.Where(x => x.TenantId == tenantId.Value); + } + + // 2. (空行后) 可选过滤:订单状态 + if (status.HasValue) + { + query = query.Where(x => x.Status == status.Value); + } + + // 3. (空行后) 可选过滤:支付状态 + if (paymentStatus.HasValue) + { + query = query.Where(x => x.PaymentStatus == paymentStatus.Value); + } + + // 4. (空行后) 排序并返回 + var orders = await query + .OrderByDescending(x => x.CreatedAt) + .ToListAsync(cancellationToken); + return orders; + } + /// public async Task> GetItemsAsync(long orderId, long tenantId, CancellationToken cancellationToken = default) { diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfProductRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfProductRepository.cs index 4e6d4c4..e63e837 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfProductRepository.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfProductRepository.cs @@ -25,6 +25,16 @@ public sealed class EfProductRepository(TakeoutAdminDbContext context) : IProduc .FirstOrDefaultAsync(cancellationToken); } + /// + public Task FindByIdAsync(long productId, CancellationToken cancellationToken = default) + { + // 1. 按主键查询(跨租户) + return context.Products + .AsNoTracking() + .Where(x => x.Id == productId) + .FirstOrDefaultAsync(cancellationToken); + } + /// public async Task> SearchAsync(long tenantId, long? storeId, long? categoryId, ProductStatus? status, CancellationToken cancellationToken = default, DateTime? updatedAfter = null) { @@ -59,6 +69,47 @@ public sealed class EfProductRepository(TakeoutAdminDbContext context) : IProduc return products; } + /// + public async Task> SearchAsync(long? tenantId, long? storeId, long? categoryId, ProductStatus? status, CancellationToken cancellationToken = default, DateTime? updatedAfter = null) + { + // 1. 构建查询(可选租户过滤) + var query = context.Products.AsNoTracking(); + if (tenantId.HasValue) + { + query = query.Where(x => x.TenantId == tenantId.Value); + } + + // 2. (空行后) 可选过滤:门店 + if (storeId.HasValue) + { + query = query.Where(x => x.StoreId == storeId.Value); + } + + // 3. (空行后) 可选过滤:分类 + if (categoryId.HasValue) + { + query = query.Where(x => x.CategoryId == categoryId.Value); + } + + // 4. (空行后) 可选过滤:状态 + if (status.HasValue) + { + query = query.Where(x => x.Status == status.Value); + } + + // 5. (空行后) 可选过滤:更新时间下限 + if (updatedAfter.HasValue) + { + query = query.Where(x => (x.UpdatedAt ?? x.CreatedAt) >= updatedAfter.Value); + } + + // 6. (空行后) 排序并返回 + var products = await query + .OrderBy(x => x.Name) + .ToListAsync(cancellationToken); + return products; + } + /// public async Task> GetCategoriesAsync(long tenantId, CancellationToken cancellationToken = default) {