From 1f1d3073435d5ea2a062a3cf2ac3f57546eb6397 Mon Sep 17 00:00:00 2001 From: Jadyn Date: Mon, 11 Jul 2022 15:02:50 +0800 Subject: [PATCH] Improvement FlashSales --- .../CreateFlashSaleOrderEventHandler.cs | 5 +- .../IFlashSalePlanAppService.cs | 4 +- .../FlashSalePlans/FlashSalePlanAppService.cs | 51 ++++---------- .../FlashSalePlans/FlashSalePlanCacheItem.cs | 3 +- ...reateFlashSaleOrderCompleteEventHandler.cs | 69 +++++++++++++++++++ .../FlashSaleResultAppService.cs | 2 - .../Products/FlashSaleInventoryManager.cs | 2 +- .../FlashSaleResultFailedReason.cs | 2 +- .../FlashSalePlans/FlashSalePlanController.cs | 10 +-- .../Products/IFlashSaleInventoryManager.cs | 2 +- .../Products/FlashSaleInventoryAppService.cs | 2 +- .../LocalFlashSaleInventoryManager.cs | 2 +- 12 files changed, 96 insertions(+), 58 deletions(-) create mode 100644 plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/RollBackInventoryCreateFlashSaleOrderCompleteEventHandler.cs diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Orders/Orders/CreateFlashSaleOrderEventHandler.cs b/plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Orders/Orders/CreateFlashSaleOrderEventHandler.cs index 7aab2d4c..57d85ec1 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Orders/Orders/CreateFlashSaleOrderEventHandler.cs +++ b/plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Orders/Orders/CreateFlashSaleOrderEventHandler.cs @@ -70,7 +70,7 @@ public class CreateFlashSaleOrderEventHandler : IDistributedEventHandler OrderAsync(Guid id, CreateOrderInput input); } diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanAppService.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanAppService.cs index a4c69347..02f21d03 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanAppService.cs +++ b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanAppService.cs @@ -112,7 +112,7 @@ public class FlashSalePlanAppService : var product = await ProductAppService.GetAsync(input.ProductId); var productSku = product.GetSkuById(input.ProductSkuId); - await ValidateCreateOrUpdateProductAsync(input.ProductId, product, input.ProductSkuId, productSku, input.StoreId); + await ValidateProductAsync(input.ProductId, product, input.StoreId); var flashSalePlan = new FlashSalePlan( GuidGenerator.Create(), @@ -120,8 +120,8 @@ public class FlashSalePlanAppService : input.StoreId, input.BeginTime, input.EndTime, - input.ProductId, - input.ProductSkuId, + product.Id, + productSku.Id, input.IsPublished ); @@ -138,7 +138,7 @@ public class FlashSalePlanAppService : await CheckMultiStorePolicyAsync(product.StoreId, UpdatePolicyName); - await ValidateCreateOrUpdateProductAsync(input.ProductId, product, input.ProductSkuId, productSku, flashSalePlan.StoreId); + await ValidateProductAsync(input.ProductId, product, flashSalePlan.StoreId); if (await ExistRelatedFlashSaleResultsAsync(id) && (input.ProductId != flashSalePlan.ProductId || input.ProductSkuId != flashSalePlan.ProductSkuId)) { @@ -146,7 +146,7 @@ public class FlashSalePlanAppService : } flashSalePlan.SetTimeRange(input.BeginTime, input.EndTime); - flashSalePlan.SetProductSku(flashSalePlan.StoreId, input.ProductId, input.ProductSkuId); + flashSalePlan.SetProductSku(flashSalePlan.StoreId, product.Id, productSku.Id); flashSalePlan.SetPublished(input.IsPublished); flashSalePlan.SetConcurrencyStampIfNotNull(input.ConcurrencyStamp); @@ -197,28 +197,7 @@ public class FlashSalePlanAppService : await SetPreOrderCacheAsync(plan, product, productSku); } - public virtual async Task CheckPreOrderAsync(Guid id) - { - var plan = await GetFlashSalePlanCacheAsync(id); - - var preOrderCache = await GetPreOrderCacheAsync(plan); - if (preOrderCache == null) - { - throw new BusinessException(FlashSalesErrorCodes.PreOrderExpired); - } - - var product = await ProductAppService.GetAsync(plan.ProductId); - var productSku = product.GetSkuById(plan.ProductSkuId); - - if (!await CompareHashTokenAsync(preOrderCache.HashToken, plan, product, productSku)) - { - throw new BusinessException(FlashSalesErrorCodes.PreOrderExpired); - } - - await ValidatePreOrderAsync(plan, product, productSku); - } - - public virtual async Task CreateOrderAsync(Guid id, CreateOrderInput input) + public virtual async Task OrderAsync(Guid id, CreateOrderInput input) { var plan = await GetFlashSalePlanCacheAsync(id); var now = Clock.Now; @@ -257,18 +236,18 @@ public class FlashSalePlanAppService : throw new BusinessException(FlashSalesErrorCodes.DuplicateFlashSalesOrder); } - if (!await FlashSaleInventoryManager.TryIncreaseInventoryAsync( + if (!await FlashSaleInventoryManager.TryReduceInventoryAsync( plan.TenantId, preOrderCache.InventoryProviderName, plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true)) { - throw new BusinessException(FlashSalesErrorCodes.ProductSkuInventoryExceeded); + return false; } var result = await CreatePendingFlashSaleResultAsync(plan, userId, async (existsResultId) => { await SetUserFlashSaleResultCacheAsync(plan, userId, existsResultId); - await FlashSaleInventoryManager.TryIncreaseInventoryAsync( + await FlashSaleInventoryManager.TryRollBackInventoryAsync( plan.TenantId, preOrderCache.InventoryProviderName, plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true ); @@ -279,6 +258,8 @@ public class FlashSalePlanAppService : var createFlashSaleOrderEto = await PrepareCreateFlashSaleOrderEtoAsync(plan, result.Id, input, userId, now, preOrderCache.HashToken); await DistributedEventBus.PublishAsync(createFlashSaleOrderEto); + + return true; } #region PreOrderCache @@ -394,18 +375,13 @@ public class FlashSalePlanAppService : return await FlashSaleResultRepository.AnyAsync(x => x.PlanId == planId); } - protected virtual Task ValidateCreateOrUpdateProductAsync(Guid productId, ProductDto product, Guid productSkuId, ProductSkuDto productSku, Guid storeId) + protected virtual Task ValidateProductAsync(Guid productId, ProductDto product, Guid storeId) { if (product.StoreId != storeId) { throw new ProductIsNotInThisStoreException(productId, storeId); } - if (productSku == null) - { - throw new ProductSkuIsNotFoundException(productSkuId); - } - if (product.InventoryStrategy != InventoryStrategy.FlashSales) { throw new UnexpectedInventoryStrategyException(InventoryStrategy.FlashSales); @@ -445,7 +421,8 @@ public class FlashSalePlanAppService : protected virtual async Task CreatePendingFlashSaleResultAsync(FlashSalePlanCacheItem plan, Guid userId, Func existResultPreProcess) { // Prevent repeat submit - var existsResult = await FlashSaleResultRepository.FirstOrDefaultAsync(x => x.PlanId == plan.Id && x.UserId == userId); + var existsResult = await FlashSaleResultRepository.FirstOrDefaultAsync(x => + x.PlanId == plan.Id && x.UserId == userId && x.Status != FlashSaleResultStatus.Failed && x.Reason != FlashSaleResultFailedReason.InvalidHashToken); if (existsResult != null) { diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanCacheItem.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanCacheItem.cs index da5e41a0..4aefcbad 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanCacheItem.cs +++ b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanCacheItem.cs @@ -1,10 +1,11 @@ using System; using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans.Dtos; +using Volo.Abp.MultiTenancy; namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; [Serializable] -public class FlashSalePlanCacheItem : FlashSalePlanDto +public class FlashSalePlanCacheItem : FlashSalePlanDto, IMultiTenant { public Guid? TenantId { get; set; } } diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/RollBackInventoryCreateFlashSaleOrderCompleteEventHandler.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/RollBackInventoryCreateFlashSaleOrderCompleteEventHandler.cs new file mode 100644 index 00000000..766f5a11 --- /dev/null +++ b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/RollBackInventoryCreateFlashSaleOrderCompleteEventHandler.cs @@ -0,0 +1,69 @@ +using System; +using System.Threading.Tasks; +using EasyAbp.Eshop.Products.Products; +using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults; +using EasyAbp.EShop.Products.Products; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Logging; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus.Distributed; + +namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans; + +public class RollBackInventoryCreateFlashSaleOrderCompleteEventHandler : IDistributedEventHandler, ITransientDependency +{ + protected IFlashSaleInventoryManager FlashSaleInventoryManager { get; } + protected IDistributedCache DistributedCache { get; } + protected IFlashSalePlanRepository FlashSalePlanRepository { get; } + protected IProductAppService ProductAppService { get; } + protected ILogger Logger { get; } + + public RollBackInventoryCreateFlashSaleOrderCompleteEventHandler( + IFlashSaleInventoryManager flashSaleInventoryManager, + IDistributedCache distributedCache, + IFlashSalePlanRepository flashSalePlanRepository, + IProductAppService productAppService) + { + FlashSaleInventoryManager = flashSaleInventoryManager; + DistributedCache = distributedCache; + FlashSalePlanRepository = flashSalePlanRepository; + ProductAppService = productAppService; + } + + public virtual async Task HandleEventAsync(CreateFlashSaleOrderCompleteEto eventData) + { + if (eventData.Success) + { + return; + } + + if (eventData.Reason != FlashSaleResultFailedReason.InvalidHashToken) + { + return; + } + + var plan = await FlashSalePlanRepository.GetAsync(eventData.PlanId); + var product = await ProductAppService.GetAsync(plan.ProductId); + + if (!await FlashSaleInventoryManager.TryRollBackInventoryAsync( + plan.TenantId, product.InventoryProviderName, + plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true + )) + { + Logger.LogWarning("Try roll back inventory failed."); + return; + } + + await RemoveUserFlashSaleResultCacheAsync(plan, eventData.UserId); + } + + protected virtual Task GetUserFlashSaleResultCacheKeyAsync(FlashSalePlan plan, Guid userId) + { + return Task.FromResult($"flash-sale-result-{plan.Id}-{userId}"); + } + + protected virtual async Task RemoveUserFlashSaleResultCacheAsync(FlashSalePlan plan, Guid userId) + { + await DistributedCache.RemoveAsync(await GetUserFlashSaleResultCacheKeyAsync(plan, userId)); + } +} diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultAppService.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultAppService.cs index 7c00f09d..20e1a1bc 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultAppService.cs +++ b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultAppService.cs @@ -16,8 +16,6 @@ public class FlashSaleResultAppService : protected override string GetPolicyName { get; set; } protected override string GetListPolicyName { get; set; } - protected IFlashSaleResultRepository FlashSaleResultRepository { get; } - public FlashSaleResultAppService(IFlashSaleResultRepository flashSaleResultRepository) : base(flashSaleResultRepository) { } diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleInventoryManager.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleInventoryManager.cs index 0d811476..6e6bd678 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleInventoryManager.cs +++ b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleInventoryManager.cs @@ -24,7 +24,7 @@ public class FlashSaleInventoryManager : IFlashSaleInventoryManager, ITransientD new ReduceInventoryInput(tenantId, providerName, storeId, productId, productSkuId, quantity, increaseSold)); } - public virtual async Task TryIncreaseInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId, + public virtual async Task TryRollBackInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId, Guid productSkuId, int quantity, bool decreaseSold) { return await FlashSaleInventoryReducerAppService.TryIncreaseAsync diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultFailedReason.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultFailedReason.cs index e18b76cb..7d2891e3 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultFailedReason.cs +++ b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSaleResults/FlashSaleResultFailedReason.cs @@ -4,5 +4,5 @@ public static class FlashSaleResultFailedReason { public const string InsufficientInventory = nameof(InsufficientInventory); - public const string PreOrderExipred = nameof(PreOrderExipred); + public const string InvalidHashToken = nameof(InvalidHashToken); } diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.HttpApi/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanController.cs b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.HttpApi/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanController.cs index 90551b2f..c2c473d6 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.HttpApi/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanController.cs +++ b/plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.HttpApi/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanController.cs @@ -57,15 +57,9 @@ public class FlashSalePlanController : return Service.PreOrderAsync(id); } - [HttpGet("{id}/check-pre-order")] - public virtual Task CheckPreOrderAsync(Guid id) - { - return Service.CheckPreOrderAsync(id); - } - [HttpPost("{id}/order")] - public virtual Task CreateOrderAsync(Guid id, CreateOrderInput input) + public virtual Task OrderAsync(Guid id, CreateOrderInput input) { - return Service.CreateOrderAsync(id, input); + return Service.OrderAsync(id, input); } } diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/EasyAbp/Eshop/Products/Products/IFlashSaleInventoryManager.cs b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/EasyAbp/Eshop/Products/Products/IFlashSaleInventoryManager.cs index 0896e7d5..2fa36304 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/EasyAbp/Eshop/Products/Products/IFlashSaleInventoryManager.cs +++ b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/EasyAbp/Eshop/Products/Products/IFlashSaleInventoryManager.cs @@ -7,5 +7,5 @@ public interface IFlashSaleInventoryManager { Task TryReduceInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId, Guid productSkuId, int quantity, bool increaseSold); - Task TryIncreaseInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId, Guid productSkuId, int quantity, bool decreaseSold); + Task TryRollBackInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId, Guid productSkuId, int quantity, bool decreaseSold); } diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleInventoryAppService.cs b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleInventoryAppService.cs index b398f4c9..cbac9f41 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleInventoryAppService.cs +++ b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleInventoryAppService.cs @@ -32,7 +32,7 @@ public class FlashSaleInventoryAppService : ProductsAppService, IFlashSaleInvent { await CheckPolicyAsync(ProductsPluginsFlashSalesPermissions.FlashSaleInventory.Increase); - return await LocalFlashSaleInventoryManager.TryIncreaseInventoryAsync( + return await LocalFlashSaleInventoryManager.TryRollBackInventoryAsync( input.TenantId, input.ProviderName, input.StoreId, diff --git a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/LocalFlashSaleInventoryManager.cs b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/LocalFlashSaleInventoryManager.cs index f13f758e..093b7ed3 100644 --- a/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/LocalFlashSaleInventoryManager.cs +++ b/plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/LocalFlashSaleInventoryManager.cs @@ -23,7 +23,7 @@ public class LocalFlashSaleInventoryManager : ILocalFlashSaleInventoryManager, I .TryReduceInventoryAsync(model, quantity, increaseSold); } - public virtual async Task TryIncreaseInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId, + public virtual async Task TryRollBackInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId, Guid productSkuId, int quantity, bool decreaseSold) { var model = new InventoryQueryModel(tenantId, storeId, productId, productSkuId);