Browse Source

Introduce `IFlashSaleInventoryManager`

pull/184/head
Jadyn 4 years ago
parent
commit
bc5fc8a6ab
  1. 16
      EShop.sln
  2. 2
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductInventoryProviderResolver.cs
  3. 8
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductInventoryProviderResolver.cs
  4. 110
      plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Orders/Orders/CreateFlashSaleOrderEventHandler.cs
  5. 36
      plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Orders/Plugins/FlashSales/EShopOrdersPluginsFlashSalesApplicationAutoMapperProfile.cs
  6. 2
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp.EShop.Plugins.FlashSales.Application.csproj
  7. 3
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesApplicationModule.cs
  8. 171
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanAppService.cs
  9. 17
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanTokenCacheItem.cs
  10. 33
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleInventoryManager.cs
  11. 6
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/CreateFlashSaleOrderEto.cs
  12. 17
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleProductAttributeEto.cs
  13. 14
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleProductAttributeOptionEto.cs
  14. 14
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleProductDetailEto.cs
  15. 44
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleProductEto.cs
  16. 31
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleProductSkuEto.cs
  17. 27
      plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleReduceInventoryEto.cs
  18. 14
      plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions.csproj
  19. 11
      plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/EasyAbp/Eshop/Products/Plugins/FlashSales/EShopProductsPluginsFlashSalesApplicationModule.cs
  20. 11
      plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/EasyAbp/Eshop/Products/Products/IFlashSaleInventoryManager.cs
  21. 3
      plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/FodyWeavers.xml
  22. 30
      plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/FodyWeavers.xsd
  23. 14
      plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts.csproj
  24. 10
      plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Plugins/FlashSales/EShopProductsPluginsFlashSalesApplicationContractsModule.cs
  25. 26
      plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Plugins/FlashSales/Permissions/ProductsPluginsFlashSalesPermissionDefinitionProvider.cs
  26. 21
      plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Plugins/FlashSales/Permissions/ProductsPluginsFlashSalesPermissions.cs
  27. 37
      plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/IncreaseInventoryInput.cs
  28. 37
      plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ReduceInventoryInput.cs
  29. 13
      plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Products/IFlashSaleInventoryAppService.cs
  30. 3
      plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/FodyWeavers.xml
  31. 30
      plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/FodyWeavers.xsd
  32. 2
      plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp.EShop.Products.Plugins.FlashSales.Application.csproj
  33. 37
      plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Plugins/FlashSales/EShopProductsPluginsFlashSalesApplicationAutoMapperProfile.cs
  34. 20
      plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Plugins/FlashSales/EShopProductsPluginsFlashSalesApplicationModule.cs
  35. 45
      plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleInventoryAppService.cs
  36. 112
      plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleReduceInventoryEventHandler.cs
  37. 12
      plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/ILocalFlashSaleInventoryManager.cs
  38. 33
      plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/LocalFlashSaleInventoryManager.cs

16
EShop.sln

@ -413,7 +413,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Plugins.Flash
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Orders.Plugins.FlashSales.Application", "plugins\FlashSales\src\EasyAbp.EShop.Orders.Plugins.FlashSales.Application\EasyAbp.EShop.Orders.Plugins.FlashSales.Application.csproj", "{5732E880-CB72-49A0-AC4F-A0620F4E4D16}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Products.Plugins.FlashSales.Application", "plugins\FlashSales\src\EasyAbp.EShop.Products.Plugins.FlashSales.Application\EasyAbp.EShop.Products.Plugins.FlashSales.Application.csproj", "{6AD2F468-D86C-4F9A-B280-3BCC15661C47}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Products.Plugins.FlashSales.Application", "plugins\FlashSales\src\EasyAbp.EShop.Products.Plugins.FlashSales.Application\EasyAbp.EShop.Products.Plugins.FlashSales.Application.csproj", "{6AD2F468-D86C-4F9A-B280-3BCC15661C47}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions", "plugins\FlashSales\src\EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions\EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions.csproj", "{C1EB9DC0-F572-41DF-9716-893B154BBB13}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts", "plugins\FlashSales\src\EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts\EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts.csproj", "{F08D9409-4D01-4639-A7B8-A70B7ED8E0F9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -1077,6 +1081,14 @@ Global
{6AD2F468-D86C-4F9A-B280-3BCC15661C47}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6AD2F468-D86C-4F9A-B280-3BCC15661C47}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6AD2F468-D86C-4F9A-B280-3BCC15661C47}.Release|Any CPU.Build.0 = Release|Any CPU
{C1EB9DC0-F572-41DF-9716-893B154BBB13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C1EB9DC0-F572-41DF-9716-893B154BBB13}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C1EB9DC0-F572-41DF-9716-893B154BBB13}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C1EB9DC0-F572-41DF-9716-893B154BBB13}.Release|Any CPU.Build.0 = Release|Any CPU
{F08D9409-4D01-4639-A7B8-A70B7ED8E0F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F08D9409-4D01-4639-A7B8-A70B7ED8E0F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F08D9409-4D01-4639-A7B8-A70B7ED8E0F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F08D9409-4D01-4639-A7B8-A70B7ED8E0F9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1284,6 +1296,8 @@ Global
{417AB8E2-1488-4814-9699-3B189D1ABA67} = {9C180C9E-50E9-4624-BE06-5C8C24A028E4}
{5732E880-CB72-49A0-AC4F-A0620F4E4D16} = {F29C5BCD-E6C0-4556-A631-CACA41B1050B}
{6AD2F468-D86C-4F9A-B280-3BCC15661C47} = {F29C5BCD-E6C0-4556-A631-CACA41B1050B}
{C1EB9DC0-F572-41DF-9716-893B154BBB13} = {F29C5BCD-E6C0-4556-A631-CACA41B1050B}
{F08D9409-4D01-4639-A7B8-A70B7ED8E0F9} = {F29C5BCD-E6C0-4556-A631-CACA41B1050B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {28315BFD-90E7-4E14-A2EA-F3D23AF4126F}

2
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductInventoryProviderResolver.cs

@ -9,4 +9,6 @@ public interface IProductInventoryProviderResolver
Task<bool> ExistProviderAsync([NotNull] string providerName);
Task<IProductInventoryProvider> GetAsync(Product product);
Task<IProductInventoryProvider> GetAsync([NotNull] string providerName);
}

8
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductInventoryProviderResolver.cs

@ -7,6 +7,7 @@ using EasyAbp.EShop.Products.ProductInventories;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp;
using Volo.Abp.DependencyInjection;
namespace EasyAbp.EShop.Products.Products;
@ -47,6 +48,11 @@ public class ProductInventoryProviderResolver : IProductInventoryProviderResolve
return Task.FromResult(GetProviderByName(options.Value.DefaultInventoryProviderName));
}
public virtual Task<IProductInventoryProvider> GetAsync([NotNull] string providerName)
{
return Task.FromResult(GetProviderByName(providerName));
}
protected virtual IProductInventoryProvider GetProviderByName([CanBeNull] string providerName)
{
if (providerName.IsNullOrEmpty())
@ -67,7 +73,7 @@ public class ProductInventoryProviderResolver : IProductInventoryProviderResolve
{
return;
}
var options = ServiceProvider.GetRequiredService<IOptions<EShopProductsOptions>>().Value;
foreach (var pair in options.InventoryProviders.GetConfigurationsDictionary())

110
plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Orders/Orders/CreateFlashSaleOrderEventHandler.cs

@ -3,7 +3,10 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using EasyAbp.EShop.Orders.Orders.Dtos;
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults;
using EasyAbp.EShop.Products.ProductDetails;
using EasyAbp.EShop.Products.ProductDetails.Dtos;
using EasyAbp.EShop.Products.Products;
using EasyAbp.EShop.Products.Products.Dtos;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed;
@ -24,45 +27,59 @@ public class CreateFlashSaleOrderEventHandler : IDistributedEventHandler<CreateF
protected IDistributedEventBus DistributedEventBus { get; }
protected IProductAppService ProductAppService { get; }
protected IProductDetailAppService ProductDetailAppService { get; }
protected IFlashSalePlanHasher FlashSalePlanHasher { get; }
public CreateFlashSaleOrderEventHandler(
INewOrderGenerator newOrderGenerator,
IObjectMapper objectMapper,
IEnumerable<IOrderDiscountProvider> orderDiscountProviders,
IOrderRepository orderRepository,
IDistributedEventBus distributedEventBus)
IDistributedEventBus distributedEventBus,
IProductAppService productAppService,
IProductDetailAppService productDetailAppService,
IFlashSalePlanHasher flashSalePlanHasher)
{
NewOrderGenerator = newOrderGenerator;
ObjectMapper = objectMapper;
OrderDiscountProviders = orderDiscountProviders;
OrderRepository = orderRepository;
DistributedEventBus = distributedEventBus;
ProductAppService = productAppService;
ProductDetailAppService = productDetailAppService;
FlashSalePlanHasher = flashSalePlanHasher;
}
[UnitOfWork(true)]
public virtual async Task HandleEventAsync(CreateFlashSaleOrderEto eventData)
{
var input = new CreateOrderDto()
var product = await ProductAppService.GetAsync(eventData.Plan.ProductId);
var productSku = product.GetSkuById(eventData.Plan.ProductSkuId);
if (!await ValidateHashTokenAsync(eventData.Plan, product, productSku, eventData.HashToken))
{
StoreId = eventData.StoreId,
CustomerRemark = eventData.CustomerRemark,
OrderLines = new List<CreateOrderLineDto>()
await DistributedEventBus.PublishAsync(new CreateFlashSaleOrderCompleteEto()
{
new CreateOrderLineDto()
{
ProductId = eventData.Plan.ProductId,
ProductSkuId = eventData.Plan.ProductSkuId,
Quantity = 1
}
}
};
var productDict = new Dictionary<Guid, ProductDto>()
{
{eventData.Product.Id, ObjectMapper.Map<FlashSaleProductEto,ProductDto>(eventData.Product)}
};
var productDetailDict = new Dictionary<Guid, ProductDetailDto>()
{
{eventData.ProductDetail.Id, ObjectMapper.Map<FlashSaleProductDetailEto,ProductDetailDto>(eventData.ProductDetail)}
};
TenantId = eventData.TenantId,
PlanId = eventData.PlanId,
OrderId = null,
UserId = eventData.UserId,
StoreId = eventData.StoreId,
PendingResultId = eventData.PendingResultId,
Success = false,
Reason = FlashSaleResultFailedReason.PreOrderExipred
});
return;
}
var input = await ConvertToCreateOrderDtoAsync(eventData);
var productDict = await GetProductDictionaryAsync(product);
var productDetailDict = await GetProductDetailDictionaryAsync(product, productSku);
var order = await NewOrderGenerator.GenerateAsync(eventData.UserId, input, productDict, productDetailDict);
@ -79,4 +96,55 @@ public class CreateFlashSaleOrderEventHandler : IDistributedEventHandler<CreateF
Success = true
});
}
protected virtual Task<Dictionary<Guid, ProductDto>> GetProductDictionaryAsync(ProductDto product)
{
var productDict = new Dictionary<Guid, ProductDto>()
{
{product.Id, product}
};
return Task.FromResult(productDict);
}
protected virtual async Task<Dictionary<Guid, ProductDetailDto>> GetProductDetailDictionaryAsync(ProductDto product, ProductSkuDto productSku)
{
var dict = new Dictionary<Guid, ProductDetailDto>();
var productDetailId = productSku.ProductDetailId ?? product.ProductDetailId;
if (productDetailId.HasValue)
{
dict.Add(productDetailId.Value, await ProductDetailAppService.GetAsync(productDetailId.Value));
}
return dict;
}
protected virtual Task<CreateOrderDto> ConvertToCreateOrderDtoAsync(CreateFlashSaleOrderEto eventData)
{
var input = new CreateOrderDto()
{
StoreId = eventData.StoreId,
CustomerRemark = eventData.CustomerRemark,
OrderLines = new List<CreateOrderLineDto>()
{
new CreateOrderLineDto()
{
ProductId = eventData.Plan.ProductId,
ProductSkuId = eventData.Plan.ProductSkuId,
Quantity = 1
}
}
};
return Task.FromResult(input);
}
protected virtual async Task<bool> ValidateHashTokenAsync(FlashSalePlanEto plan, ProductDto product, ProductSkuDto productSku, string originHashToken)
{
var hashToken = await FlashSalePlanHasher.HashAsync(plan.LastModificationTime, product.LastModificationTime, productSku.LastModificationTime);
return string.Equals(hashToken, originHashToken, StringComparison.InvariantCulture);
}
}

36
plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Orders/Plugins/FlashSales/EShopOrdersPluginsFlashSalesApplicationAutoMapperProfile.cs

@ -1,36 +0,0 @@
using AutoMapper;
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
using EasyAbp.EShop.Products.ProductDetails.Dtos;
using EasyAbp.EShop.Products.Products.Dtos;
using Volo.Abp.AutoMapper;
using Volo.Abp.DependencyInjection;
namespace EasyAbp.EShop.Orders.Plugins.FlashSales
{
public class EShopOrdersPluginsFlashSalesApplicationAutoMapperProfile : Profile
{
public EShopOrdersPluginsFlashSalesApplicationAutoMapperProfile()
{
/* You can configure your AutoMapper mapping configuration here.
* Alternatively, you can split your mapping configurations
* into multiple profile classes for a better organization. */
CreateMap<FlashSaleProductEto, ProductDto>(MemberList.Destination)
.Ignore(dto => dto.Sold)
.Ignore(dto => dto.MinimumPrice)
.Ignore(dto => dto.MaximumPrice)
.MapExtraProperties();
CreateMap<FlashSaleProductSkuEto, ProductSkuDto>(MemberList.Destination)
.ForSourceMember(entity => entity.SerializedAttributeOptionIds, opt => opt.DoNotValidate())
.Ignore(dto => dto.DiscountedPrice)
.Ignore(dto => dto.Inventory)
.Ignore(dto => dto.Sold)
.MapExtraProperties();
CreateMap<FlashSaleProductAttributeEto, ProductAttributeDto>(MemberList.Destination)
.MapExtraProperties();
CreateMap<FlashSaleProductAttributeOptionEto, ProductAttributeOptionDto>(MemberList.Destination)
.MapExtraProperties();
CreateMap<FlashSaleProductDetailEto, ProductDetailDto>(MemberList.Destination)
.MapExtraProperties();
}
}
}

2
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp.EShop.Plugins.FlashSales.Application.csproj

@ -16,6 +16,8 @@
<ProjectReference Include="..\..\..\..\modules\EasyAbp.EShop.Stores\src\EasyAbp.EShop.Stores.Application.Shared\EasyAbp.EShop.Stores.Application.Shared.csproj" />
<ProjectReference Include="..\EasyAbp.EShop.Plugins.FlashSales.Application.Contracts\EasyAbp.EShop.Plugins.FlashSales.Application.Contracts.csproj" />
<ProjectReference Include="..\EasyAbp.EShop.Plugins.FlashSales.Domain\EasyAbp.EShop.Plugins.FlashSales.Domain.csproj" />
<ProjectReference Include="..\EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions\EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions.csproj" />
<ProjectReference Include="..\EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts\EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts.csproj" />
</ItemGroup>
</Project>

3
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/EShopPluginsFlashSalesApplicationModule.cs

@ -1,4 +1,5 @@
using EasyAbp.EShop.Products;
using EasyAbp.EShop.Products.Plugins.FlashSales;
using EasyAbp.EShop.Stores;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Application;
@ -13,6 +14,8 @@ namespace EasyAbp.EShop.Plugins.FlashSales;
typeof(EShopPluginsFlashSalesDomainModule),
typeof(EShopPluginsFlashSalesApplicationContractsModule),
typeof(EShopStoresApplicationSharedModule),
typeof(EShopProductsPluginsFlashSalesAbstractionsModule),
typeof(EShopProductsPluginsFlashSalesApplicationContractsModule),
typeof(AbpDddApplicationModule),
typeof(AbpAutoMapperModule),
typeof(AbpCachingModule)

171
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanAppService.cs

@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using EasyAbp.Eshop.Products.Products;
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans.Dtos;
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults;
using EasyAbp.EShop.Plugins.FlashSales.Permissions;
@ -37,9 +38,9 @@ public class FlashSalePlanAppService :
protected IProductAppService ProductAppService { get; }
protected IDistributedCache TokenDistributedCache { get; }
protected IDistributedCache<FlashSalePlanPreOrderCacheItem> PreOrderDistributedCache { get; }
protected IDistributedCache<FlashSalePlanCacheItem, Guid> DistributedCache { get; }
protected IDistributedCache<FlashSalePlanCacheItem, Guid> PlanDistributedCache { get; }
protected IDistributedEventBus DistributedEventBus { get; }
@ -49,25 +50,33 @@ public class FlashSalePlanAppService :
protected IFlashSalePlanHasher FlashSalePlanHasher { get; }
protected IFlashSaleInventoryManager FlashSaleInventoryManager { get; }
protected IDistributedCache DistributedCache { get; }
public FlashSalePlanAppService(
IFlashSalePlanRepository flashSalePlanRepository,
IProductAppService productAppService,
IDistributedCache tokenDistributedCache,
IDistributedCache<FlashSalePlanCacheItem, Guid> distributedCache,
IDistributedCache<FlashSalePlanPreOrderCacheItem> tokenDistributedCache,
IDistributedCache<FlashSalePlanCacheItem, Guid> planDistributedCache,
IDistributedEventBus distributedEventBus,
IFlashSaleResultRepository flashSaleResultRepository,
IAbpDistributedLock distributedLock,
IFlashSalePlanHasher flashSalePlanHasher)
IFlashSalePlanHasher flashSalePlanHasher,
IFlashSaleInventoryManager flashSaleInventoryManager,
IDistributedCache distributedCache)
: base(flashSalePlanRepository)
{
FlashSalePlanRepository = flashSalePlanRepository;
ProductAppService = productAppService;
TokenDistributedCache = tokenDistributedCache;
DistributedCache = distributedCache;
PreOrderDistributedCache = tokenDistributedCache;
PlanDistributedCache = planDistributedCache;
DistributedEventBus = distributedEventBus;
FlashSaleResultRepository = flashSaleResultRepository;
DistributedLock = distributedLock;
FlashSalePlanHasher = flashSalePlanHasher;
FlashSaleInventoryManager = flashSaleInventoryManager;
DistributedCache = distributedCache;
}
public override async Task<FlashSalePlanDto> GetAsync(Guid id)
@ -180,15 +189,15 @@ public class FlashSalePlanAppService :
await ValidatePreOrderAsync(plan, product, productSku);
await SetCacheHashTokenAsync(plan, product, productSku);
await SetPreOrderCacheAsync(plan, product, productSku);
}
public virtual async Task CheckPreOrderAsync(Guid id)
{
var plan = await GetFlashSalePlanCacheAsync(id);
var cacheHashToken = await GetCacheHashTokenAsync(plan);
if (cacheHashToken.IsNullOrWhiteSpace())
var preOrderCache = await GetPreOrderCacheAsync(plan);
if (preOrderCache == null)
{
throw new BusinessException(FlashSalesErrorCodes.PreOrderExpired);
}
@ -196,7 +205,7 @@ public class FlashSalePlanAppService :
var product = await ProductAppService.GetAsync(plan.ProductId);
var productSku = product.GetSkuById(plan.ProductSkuId);
if (!await CompareHashTokenAsync(cacheHashToken, plan, product, productSku))
if (!await CompareHashTokenAsync(preOrderCache.HashToken, plan, product, productSku))
{
throw new BusinessException(FlashSalesErrorCodes.PreOrderExpired);
}
@ -218,39 +227,81 @@ public class FlashSalePlanAppService :
throw new BusinessException(FlashSalesErrorCodes.FlashSaleIsOver);
}
var cacheHashToken = await GetCacheHashTokenAsync(plan);
if (cacheHashToken.IsNullOrWhiteSpace())
var preOrderCache = await GetPreOrderCacheAsync(plan);
if (preOrderCache == null)
{
throw new BusinessException(FlashSalesErrorCodes.PreOrderExpired);
}
await RemoveCacheHashTokenAsync(plan);
await RemovePreOrderCacheAsync(plan);
var userId = CurrentUser.GetId();
var result = await CreatePendingFlashSaleResultAsync(plan, userId);
var key = $"create-flash-sale-order-{plan.Id}-{userId}";
await using var handle = await DistributedLock.TryAcquireAsync(key);
if (handle == null)
{
throw new BusinessException(FlashSalesErrorCodes.BusyToCreateFlashSaleOrder);
}
var userFlashSaleResultCache = await GetUserFlashSaleResultCacheAsync(plan, userId);
if (!userFlashSaleResultCache.IsNullOrWhiteSpace())
{
throw new BusinessException(FlashSalesErrorCodes.DuplicateFlashSalesOrder);
}
if (!await FlashSaleInventoryManager.TryIncreaseInventoryAsync(
plan.TenantId, preOrderCache.InventoryProviderName,
plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true))
{
throw new BusinessException(FlashSalesErrorCodes.ProductSkuInventoryExceeded);
}
var flashSalesReduceInventoryEto = await PrepareFlashSalesReduceInventoryEtoAsync(plan, result.Id, input, userId, now, cacheHashToken);
// Prevent repeat submit
var existsResult = await FlashSaleResultRepository.FirstOrDefaultAsync(x => x.PlanId == plan.Id && x.UserId == userId);
/*
* FlashSalesReduceInventoryEto(success) -> CreateFlashSalesOrderEto -> CreateFlashSalesOrderCompleteEto
* FlashSalesReduceInventoryEto(failed) -> CreateFlashSalesOrderCompleteEto
*/
await DistributedEventBus.PublishAsync(flashSalesReduceInventoryEto);
if (existsResult != null)
{
await SetUserFlashSaleResultCacheAsync(plan, userId, existsResult.Id);
await FlashSaleInventoryManager.TryIncreaseInventoryAsync(
plan.TenantId, preOrderCache.InventoryProviderName,
plan.StoreId, plan.ProductId, plan.ProductSkuId, 1, true
);
throw new BusinessException(FlashSalesErrorCodes.DuplicateFlashSalesOrder);
}
var result = new FlashSaleResult(
id: GuidGenerator.Create(),
tenantId: CurrentTenant.Id,
storeId: plan.StoreId,
planId: plan.Id,
status: FlashSaleResultStatus.Pending,
reason: null,
userId: userId,
orderId: null
);
await FlashSaleResultRepository.InsertAsync(result, autoSave: true);
await SetUserFlashSaleResultCacheAsync(plan, userId, result.Id);
var createFlashSaleOrderEto = await PrepareCreateFlashSaleOrderEtoAsync(plan, result.Id, input, userId, now, preOrderCache.HashToken);
await DistributedEventBus.PublishAsync(createFlashSaleOrderEto);
}
protected virtual Task<FlashSaleReduceInventoryEto> PrepareFlashSalesReduceInventoryEtoAsync(
FlashSalePlanCacheItem plan,
Guid resultId,
CreateOrderInput input,
Guid userId,
DateTime now,
string hashToken)
protected virtual Task<CreateFlashSaleOrderEto> PrepareCreateFlashSaleOrderEtoAsync(
FlashSalePlanCacheItem plan, Guid resultId, CreateOrderInput input,
Guid userId, DateTime now, string hashToken)
{
var planEto = ObjectMapper.Map<FlashSalePlanCacheItem, FlashSalePlanEto>(plan);
planEto.TenantId = CurrentTenant.Id;
var eto = new FlashSaleReduceInventoryEto()
var eto = new CreateFlashSaleOrderEto()
{
TenantId = CurrentTenant.Id,
PlanId = plan.Id,
@ -273,33 +324,40 @@ public class FlashSalePlanAppService :
protected virtual async Task<FlashSalePlanCacheItem> GetFlashSalePlanCacheAsync(Guid id)
{
return await DistributedCache.GetOrAddAsync(id, async () =>
return await PlanDistributedCache.GetOrAddAsync(id, async () =>
{
var flashSalePlan = await FlashSalePlanRepository.GetAsync(id);
return ObjectMapper.Map<FlashSalePlan, FlashSalePlanCacheItem>(flashSalePlan);
});
}
protected virtual Task<string> GetCacheKeyAsync(FlashSalePlanCacheItem plan)
protected virtual Task<string> GetPreOrderCacheKeyAsync(FlashSalePlanCacheItem plan)
{
return Task.FromResult($"eshopflashsales_{CurrentTenant.Id}_{CurrentUser.Id}_{plan.ProductSkuId}");
}
protected virtual async Task<string> GetCacheHashTokenAsync(FlashSalePlanCacheItem plan)
protected virtual async Task<FlashSalePlanPreOrderCacheItem> GetPreOrderCacheAsync(FlashSalePlanCacheItem plan)
{
return await TokenDistributedCache.GetStringAsync(await GetCacheKeyAsync(plan));
return await PreOrderDistributedCache.GetAsync(await GetPreOrderCacheKeyAsync(plan));
}
protected virtual async Task RemoveCacheHashTokenAsync(FlashSalePlanCacheItem plan)
protected virtual async Task RemovePreOrderCacheAsync(FlashSalePlanCacheItem plan)
{
await TokenDistributedCache.RemoveAsync(await GetCacheKeyAsync(plan));
await PreOrderDistributedCache.RemoveAsync(await GetPreOrderCacheKeyAsync(plan));
}
protected virtual async Task SetCacheHashTokenAsync(FlashSalePlanCacheItem plan, ProductDto product, ProductSkuDto productSku)
protected virtual async Task SetPreOrderCacheAsync(FlashSalePlanCacheItem plan, ProductDto product, ProductSkuDto productSku)
{
var hashToken = await FlashSalePlanHasher.HashAsync(plan.LastModificationTime, product.LastModificationTime, productSku.LastModificationTime);
await TokenDistributedCache.SetStringAsync(await GetCacheKeyAsync(plan), hashToken, new DistributedCacheEntryOptions()
await PreOrderDistributedCache.SetAsync(await GetPreOrderCacheKeyAsync(plan), new FlashSalePlanPreOrderCacheItem()
{
HashToken = hashToken,
PlanId = plan.Id,
ProductId = product.Id,
ProductSkuId = productSku.Id,
InventoryProviderName = product.InventoryProviderName,
}, new DistributedCacheEntryOptions()
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(3)
});
@ -317,36 +375,21 @@ public class FlashSalePlanAppService :
return cacheHashToken == hashToken;
}
protected virtual async Task<FlashSaleResult> CreatePendingFlashSaleResultAsync(FlashSalePlanCacheItem plan, Guid userId)
protected virtual Task<string> GetUserFlashSaleResultCacheKeyAsync(FlashSalePlanCacheItem plan, Guid userId)
{
var lockKey = $"create-flash-sales-order-{plan.Id}-{userId}";
await using var handle = await DistributedLock.TryAcquireAsync(lockKey);
if (handle == null)
{
throw new BusinessException(FlashSalesErrorCodes.BusyToCreateFlashSaleOrder);
}
return Task.FromResult($"flash-sale-result-{plan.Id}-{userId}");
}
// Prevent repeat submit
if (await FlashSaleResultRepository.AnyAsync(x =>
x.PlanId == plan.Id && x.UserId == userId && (x.Status == FlashSaleResultStatus.Successful || x.Status == FlashSaleResultStatus.Pending))
)
{
throw new BusinessException(FlashSalesErrorCodes.DuplicateFlashSalesOrder);
}
protected virtual async Task<string> GetUserFlashSaleResultCacheAsync(FlashSalePlanCacheItem plan, Guid userId)
{
var userFlashSaleResultCacheKey = await GetUserFlashSaleResultCacheKeyAsync(plan, userId);
return await DistributedCache.GetStringAsync(userFlashSaleResultCacheKey);
}
var result = new FlashSaleResult(
id: GuidGenerator.Create(),
tenantId: CurrentTenant.Id,
storeId: plan.StoreId,
planId: plan.Id,
status: FlashSaleResultStatus.Pending,
reason: null,
userId: userId,
orderId: null
);
return await FlashSaleResultRepository.InsertAsync(result, autoSave: true);
protected virtual async Task SetUserFlashSaleResultCacheAsync(FlashSalePlanCacheItem plan, Guid userId, Guid resultId)
{
var userFlashSaleResultCacheKey = await GetUserFlashSaleResultCacheKeyAsync(plan, userId);
await DistributedCache.SetStringAsync(userFlashSaleResultCacheKey, resultId.ToString());
}
protected virtual Task ValidatePreOrderAsync(FlashSalePlanCacheItem plan, ProductDto product, ProductSkuDto productSku)

17
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSalePlanTokenCacheItem.cs

@ -0,0 +1,17 @@
using System;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
[Serializable]
public class FlashSalePlanPreOrderCacheItem
{
public string HashToken { get; set; }
public Guid PlanId { get; set; }
public Guid ProductId { get; set; }
public Guid ProductSkuId { get; set; }
public string InventoryProviderName { get; set; }
}

33
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleInventoryManager.cs

@ -0,0 +1,33 @@
using System;
using System.Threading.Tasks;
using EasyAbp.Eshop.Products.Products;
using EasyAbp.EShop.Products.Products.Dtos;
using Volo.Abp.DependencyInjection;
namespace EasyAbp.EShop.Products.Products;
[ExposeServices(typeof(IFlashSaleInventoryManager), IncludeSelf = true)]
[Dependency(TryRegister = true)]
public class FlashSaleInventoryManager : IFlashSaleInventoryManager, ITransientDependency
{
protected IFlashSaleInventoryAppService FlashSaleInventoryReducerAppService { get; }
public FlashSaleInventoryManager(IFlashSaleInventoryAppService flashSaleInventoryReducerAppService)
{
FlashSaleInventoryReducerAppService = flashSaleInventoryReducerAppService;
}
public virtual async Task<bool> TryReduceInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId,
Guid productSkuId, int quantity, bool increaseSold)
{
return await FlashSaleInventoryReducerAppService.TryReduceInventoryAsync(
new ReduceInventoryInput(tenantId, providerName, storeId, productId, productSkuId, quantity, increaseSold));
}
public virtual async Task<bool> TryIncreaseInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId,
Guid productSkuId, int quantity, bool decreaseSold)
{
return await FlashSaleInventoryReducerAppService.TryIncreaseInventoryAsync
(new IncreaseInventoryInput(tenantId, providerName, storeId, productId, productSkuId, quantity, decreaseSold));
}
}

6
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/CreateFlashSaleOrderEto.cs

@ -21,9 +21,7 @@ public class CreateFlashSaleOrderEto : ExtensibleObject, IMultiTenant
public string CustomerRemark { get; set; }
public FlashSaleProductEto Product { get; set; }
public FlashSaleProductDetailEto ProductDetail { get; set; }
public FlashSalePlanEto Plan { get; set; }
public string HashToken { get; set; }
}

17
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleProductAttributeEto.cs

@ -1,17 +0,0 @@
using System;
using System.Collections.Generic;
using EasyAbp.EShop.Products.Products;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
[Serializable]
public class FlashSaleProductAttributeEto : FullAuditedEntityEto<Guid>, IProductAttribute
{
public string DisplayName { get; set; }
public string Description { get; set; }
public int DisplayOrder { get; set; }
public List<FlashSaleProductAttributeOptionEto> ProductAttributeOptions { get; set; }
}

14
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleProductAttributeOptionEto.cs

@ -1,14 +0,0 @@
using System;
using EasyAbp.EShop.Products.Products;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
[Serializable]
public class FlashSaleProductAttributeOptionEto : FullAuditedEntityEto<Guid>, IProductAttributeOption
{
public string DisplayName { get; set; }
public string Description { get; set; }
public int DisplayOrder { get; set; }
}

14
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleProductDetailEto.cs

@ -1,14 +0,0 @@
using System;
using Volo.Abp.MultiTenancy;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
[Serializable]
public class FlashSaleProductDetailEto : FullAuditedEntityEto<Guid>, IMultiTenant
{
public Guid? TenantId { get; set; }
public Guid? StoreId { get; set; }
public string Description { get; set; }
}

44
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleProductEto.cs

@ -1,44 +0,0 @@
using System;
using System.Collections.Generic;
using EasyAbp.EShop.Products.Products;
using Volo.Abp.MultiTenancy;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
[Serializable]
public class FlashSaleProductEto : FullAuditedEntityEto<Guid>, IProduct, IMultiTenant
{
public Guid? TenantId { get; set; }
public Guid StoreId { get; set; }
public string ProductGroupName { get; set; }
public string ProductGroupDisplayName { get; set; }
public Guid? ProductDetailId { get; set; }
public string UniqueName { get; set; }
public string DisplayName { get; set; }
public InventoryStrategy InventoryStrategy { get; set; }
public string InventoryProviderName { get; set; }
public string MediaResources { get; set; }
public int DisplayOrder { get; set; }
public bool IsPublished { get; set; }
public bool IsStatic { get; set; }
public bool IsHidden { get; set; }
public TimeSpan? PaymentExpireIn { get; set; }
public List<FlashSaleProductAttributeEto> ProductAttributes { get; set; }
public List<FlashSaleProductSkuEto> ProductSkus { get; set; }
}

31
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleProductSkuEto.cs

@ -1,31 +0,0 @@
using System;
using System.Collections.Generic;
using EasyAbp.EShop.Products.Products;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
[Serializable]
public class FlashSaleProductSkuEto : FullAuditedEntityEto<Guid>, IProductSku
{
public List<Guid> AttributeOptionIds { get; set; }
public string SerializedAttributeOptionIds { get; set; }
public string Name { get; set; }
public string Currency { get; set; }
public decimal? OriginalPrice { get; set; }
public decimal Price { get; set; }
public int OrderMinQuantity { get; set; }
public int OrderMaxQuantity { get; set; }
public TimeSpan? PaymentExpireIn { get; set; }
public string MediaResources { get; set; }
public Guid? ProductDetailId { get; set; }
}

27
plugins/FlashSales/src/EasyAbp.EShop.Plugins.FlashSales.Domain.Shared/EasyAbp/EShop/Plugins/FlashSales/FlashSalePlans/FlashSaleReduceInventoryEto.cs

@ -1,27 +0,0 @@
using System;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectExtending;
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
[Serializable]
public class FlashSaleReduceInventoryEto : ExtensibleObject, IMultiTenant
{
public Guid? TenantId { get; set; }
public Guid StoreId { get; set; }
public Guid PlanId { get; set; }
public Guid UserId { get; set; }
public Guid PendingResultId { get; set; }
public DateTime CreateTime { get; set; }
public string CustomerRemark { get; set; }
public FlashSalePlanEto Plan { get; set; }
public string HashToken { get; set; }
}

14
plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions.csproj

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\modules\EasyAbp.EShop.Products\src\EasyAbp.EShop.Products.Application.Contracts\EasyAbp.EShop.Products.Application.Contracts.csproj" />
</ItemGroup>
</Project>

11
plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/EasyAbp/Eshop/Products/Plugins/FlashSales/EShopProductsPluginsFlashSalesApplicationModule.cs

@ -0,0 +1,11 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
namespace EasyAbp.EShop.Products.Plugins.FlashSales;
[DependsOn(
typeof(EShopProductsApplicationContractsModule)
)]
public class EShopProductsPluginsFlashSalesAbstractionsModule : AbpModule
{
}

11
plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/EasyAbp/Eshop/Products/Products/IFlashSaleInventoryManager.cs

@ -0,0 +1,11 @@
using System;
using System.Threading.Tasks;
namespace EasyAbp.Eshop.Products.Products;
public interface IFlashSaleInventoryManager
{
Task<bool> TryReduceInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId, Guid productSkuId, int quantity, bool increaseSold);
Task<bool> TryIncreaseInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId, Guid productSkuId, int quantity, bool decreaseSold);
}

3
plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait />
</Weavers>

30
plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

14
plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts.csproj

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\modules\EasyAbp.EShop.Products\src\EasyAbp.EShop.Products.Application.Contracts\EasyAbp.EShop.Products.Application.Contracts.csproj" />
</ItemGroup>
</Project>

10
plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Plugins/FlashSales/EShopProductsPluginsFlashSalesApplicationContractsModule.cs

@ -0,0 +1,10 @@
using Volo.Abp.Modularity;
namespace EasyAbp.EShop.Products.Plugins.FlashSales;
[DependsOn(
typeof(EShopProductsApplicationContractsModule)
)]
public class EShopProductsPluginsFlashSalesApplicationContractsModule : AbpModule
{
}

26
plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Plugins/FlashSales/Permissions/ProductsPluginsFlashSalesPermissionDefinitionProvider.cs

@ -0,0 +1,26 @@
using EasyAbp.EShop.Products.Localization;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Localization;
namespace EasyAbp.EShop.Products.Permissions
{
public class ProductsPluginsFlashSalesPermissionDefinitionProvider : PermissionDefinitionProvider
{
public override void Define(IPermissionDefinitionContext context)
{
var moduleGroup = context.AddGroup(ProductsPluginsFlashSalesPermissions.GroupName, L("Permission:Products"));
var flashSaleInventoryPermission = moduleGroup.AddPermission(ProductsPluginsFlashSalesPermissions.FlashSaleInventory.Default, L("Permission:FlashSaleInventory"))
.WithProviders(ClientPermissionValueProvider.ProviderName);
flashSaleInventoryPermission.AddChild(ProductsPluginsFlashSalesPermissions.FlashSaleInventory.Increase, L("Permission:InventoryIncrease"))
.WithProviders(ClientPermissionValueProvider.ProviderName);
flashSaleInventoryPermission.AddChild(ProductsPluginsFlashSalesPermissions.FlashSaleInventory.Reduce, L("Permission:InventoryReduce"))
.WithProviders(ClientPermissionValueProvider.ProviderName);
}
private static LocalizableString L(string name)
{
return LocalizableString.Create<ProductsResource>(name);
}
}
}

21
plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Plugins/FlashSales/Permissions/ProductsPluginsFlashSalesPermissions.cs

@ -0,0 +1,21 @@
using Volo.Abp.Reflection;
namespace EasyAbp.EShop.Products.Permissions
{
public class ProductsPluginsFlashSalesPermissions
{
public const string GroupName = "EasyAbp.EShop.Products.Plugins.FlashSales";
public class FlashSaleInventory
{
public const string Default = GroupName + ".FlashSaleInventory";
public const string Reduce = Default + ".Reduce";
public const string Increase = Default + ".Increase";
}
public static string[] GetAll()
{
return ReflectionHelper.GetPublicConstantsRecursively(typeof(ProductsPluginsFlashSalesPermissions));
}
}
}

37
plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/IncreaseInventoryInput.cs

@ -0,0 +1,37 @@
using System;
using EasyAbp.EShop.Stores.Stores;
using Volo.Abp.MultiTenancy;
namespace EasyAbp.EShop.Products.Products.Dtos;
public class IncreaseInventoryInput : IMultiTenant, IMultiStore
{
public Guid? TenantId { get; set; }
public string ProviderName { get; set; }
public Guid StoreId { get; set; }
public Guid ProductId { get; set; }
public Guid ProductSkuId { get; set; }
public int Quantity { get; set; }
public bool ReduceSold { get; set; }
public IncreaseInventoryInput()
{
}
public IncreaseInventoryInput(Guid? tenantId, string providerName, Guid storeId, Guid productId, Guid productSkuId, int quantity, bool reduceSold)
{
TenantId = tenantId;
ProviderName = providerName;
StoreId = storeId;
ProductId = productId;
ProductSkuId = productSkuId;
Quantity = quantity;
ReduceSold = reduceSold;
}
}

37
plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Products/Dtos/ReduceInventoryInput.cs

@ -0,0 +1,37 @@
using System;
using EasyAbp.EShop.Stores.Stores;
using Volo.Abp.MultiTenancy;
namespace EasyAbp.EShop.Products.Products.Dtos;
public class ReduceInventoryInput : IMultiTenant, IMultiStore
{
public Guid? TenantId { get; set; }
public string ProviderName { get; set; }
public Guid StoreId { get; set; }
public Guid ProductId { get; set; }
public Guid ProductSkuId { get; set; }
public int Quantity { get; set; }
public bool IncreaseSold { get; set; }
public ReduceInventoryInput()
{
}
public ReduceInventoryInput(Guid? tenantId, string providerName, Guid storeId, Guid productId, Guid productSkuId, int quantity, bool increaseSold)
{
TenantId = tenantId;
ProviderName = providerName;
StoreId = storeId;
ProductId = productId;
ProductSkuId = productSkuId;
Quantity = quantity;
IncreaseSold = increaseSold;
}
}

13
plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/EasyAbp/EShop/Products/Products/IFlashSaleInventoryAppService.cs

@ -0,0 +1,13 @@
using System;
using System.Threading.Tasks;
using EasyAbp.EShop.Products.Products.Dtos;
using Volo.Abp.Application.Services;
namespace EasyAbp.EShop.Products.Products;
public interface IFlashSaleInventoryAppService : IApplicationService
{
Task<bool> TryReduceInventoryAsync(ReduceInventoryInput input);
Task<bool> TryIncreaseInventoryAsync(IncreaseInventoryInput input);
}

3
plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait />
</Weavers>

30
plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

2
plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp.EShop.Products.Plugins.FlashSales.Application.csproj

@ -10,6 +10,8 @@
<ItemGroup>
<ProjectReference Include="..\..\..\..\modules\EasyAbp.EShop.Products\src\EasyAbp.EShop.Products.Application\EasyAbp.EShop.Products.Application.csproj" />
<ProjectReference Include="..\EasyAbp.EShop.Plugins.FlashSales.Application.Contracts\EasyAbp.EShop.Plugins.FlashSales.Application.Contracts.csproj" />
<ProjectReference Include="..\EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions\EasyAbp.EShop.Products.Plugins.FlashSales.Abstractions.csproj" />
<ProjectReference Include="..\EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts\EasyAbp.EShop.Products.Plugins.FlashSales.Application.Contracts.csproj" />
</ItemGroup>
</Project>

37
plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Plugins/FlashSales/EShopProductsPluginsFlashSalesApplicationAutoMapperProfile.cs

@ -1,37 +0,0 @@
using System.Linq;
using AutoMapper;
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
using EasyAbp.EShop.Products.Options;
using EasyAbp.EShop.Products.ProductDetails;
using EasyAbp.EShop.Products.Products;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
namespace EasyAbp.EShop.Plugins.FlashSales;
public class EShopProductsPluginsFlashSalesApplicationAutoMapperProfile : Profile, ISingletonDependency
{
public EShopProductsPluginsFlashSalesApplicationAutoMapperProfile(
IAttributeOptionIdsSerializer attributeOptionIdsSerializer,
IOptionsMonitor<EShopProductsOptions> options)
{
CreateMap<Product, FlashSaleProductEto>()
.ForMember(x => x.ProductGroupDisplayName, opt => opt.Ignore())
.AfterMap((src, dest) =>
{
var dict = options.CurrentValue.Groups.GetConfigurationsDictionary();
dest.ProductGroupDisplayName = dict[src.ProductGroupName].DisplayName;
})
.MapExtraProperties();
CreateMap<ProductSku, FlashSaleProductSkuEto>()
.ForMember(x => x.AttributeOptionIds, opt => opt.Ignore())
.AfterMap(async (src, dest) => dest.AttributeOptionIds = (await attributeOptionIdsSerializer.DeserializeAsync(src.SerializedAttributeOptionIds)).ToList())
.MapExtraProperties();
CreateMap<ProductAttribute, FlashSaleProductAttributeEto>()
.MapExtraProperties();
CreateMap<ProductAttributeOption, FlashSaleProductAttributeOptionEto>()
.MapExtraProperties();
CreateMap<ProductDetail, FlashSaleProductDetailEto>()
.MapExtraProperties();
}
}

20
plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Plugins/FlashSales/EShopProductsPluginsFlashSalesApplicationModule.cs

@ -1,28 +1,14 @@
using EasyAbp.EShop.Plugins.FlashSales;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AutoMapper;
using Volo.Abp.Modularity;
namespace EasyAbp.EShop.Products.Plugins.FlashSales;
[DependsOn(
typeof(EShopProductsApplicationModule),
typeof(EShopPluginsFlashSalesApplicationContractsModule)
typeof(EShopPluginsFlashSalesApplicationContractsModule),
typeof(EShopProductsPluginsFlashSalesApplicationContractsModule),
typeof(EShopProductsPluginsFlashSalesAbstractionsModule)
)]
public class EShopProductsPluginsFlashSalesApplicationModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAutoMapperObjectMapper<EShopProductsPluginsFlashSalesApplicationModule>();
Configure<AbpAutoMapperOptions>(options =>
{
options.Configurators.Add(abpAutoMapperConfigurationContext =>
{
var profile = abpAutoMapperConfigurationContext.ServiceProvider
.GetRequiredService<EShopProductsPluginsFlashSalesApplicationAutoMapperProfile>();
abpAutoMapperConfigurationContext.MapperConfiguration.AddProfile(profile);
});
});
}
}

45
plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleInventoryAppService.cs

@ -0,0 +1,45 @@
using System.Threading.Tasks;
using EasyAbp.EShop.Products.Permissions;
using EasyAbp.EShop.Products.Products.Dtos;
namespace EasyAbp.EShop.Products.Products;
public class FlashSaleInventoryAppService : ProductsAppService, IFlashSaleInventoryAppService
{
protected ILocalFlashSaleInventoryManager LocalFlashSaleInventoryReducer { get; }
public FlashSaleInventoryAppService(ILocalFlashSaleInventoryManager localFlashSaleInventoryReducer)
{
LocalFlashSaleInventoryReducer = localFlashSaleInventoryReducer;
}
public virtual async Task<bool> TryReduceInventoryAsync(ReduceInventoryInput input)
{
await CheckPolicyAsync(ProductsPluginsFlashSalesPermissions.FlashSaleInventory.Reduce);
return await LocalFlashSaleInventoryReducer.TryReduceInventoryAsync(
input.TenantId,
input.ProviderName,
input.StoreId,
input.ProductId,
input.ProductSkuId,
input.Quantity,
input.IncreaseSold
);
}
public virtual async Task<bool> TryIncreaseInventoryAsync(IncreaseInventoryInput input)
{
await CheckPolicyAsync(ProductsPluginsFlashSalesPermissions.FlashSaleInventory.Increase);
return await LocalFlashSaleInventoryReducer.TryIncreaseInventoryAsync(
input.TenantId,
input.ProviderName,
input.StoreId,
input.ProductId,
input.ProductSkuId,
input.Quantity,
input.ReduceSold
);
}
}

112
plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/FlashSaleReduceInventoryEventHandler.cs

@ -1,112 +0,0 @@
using System.Linq;
using System.Threading.Tasks;
using EasyAbp.EShop.Plugins.FlashSales.FlashSalePlans;
using EasyAbp.EShop.Plugins.FlashSales.FlashSaleResults;
using EasyAbp.EShop.Products.ProductDetails;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Uow;
namespace EasyAbp.EShop.Products.Products;
public class FlashSaleReduceInventoryEventHandler : IDistributedEventHandler<FlashSaleReduceInventoryEto>, ITransientDependency
{
protected IProductRepository ProductRepository { get; }
protected IProductDetailRepository ProductDetailRepository { get; }
protected ProductManager ProductManager { get; }
protected IDistributedEventBus DistributedEventBus { get; }
protected IObjectMapper ObjectMapper { get; }
protected IFlashSalePlanHasher FlashSalePlanHasher { get; }
public FlashSaleReduceInventoryEventHandler(
IProductRepository productRepository,
IProductDetailRepository productDetailRepository,
ProductManager productManager,
IDistributedEventBus distributedEventBus,
IObjectMapper objectMapper,
IFlashSalePlanHasher flashSalePlanHasher)
{
ProductRepository = productRepository;
ProductDetailRepository = productDetailRepository;
ProductManager = productManager;
DistributedEventBus = distributedEventBus;
ObjectMapper = objectMapper;
FlashSalePlanHasher = flashSalePlanHasher;
}
[UnitOfWork]
public virtual async Task HandleEventAsync(FlashSaleReduceInventoryEto eventData)
{
var product = await ProductRepository.GetAsync(eventData.Plan.ProductId);
var productSku = product.ProductSkus.Single(x => x.Id == eventData.Plan.ProductSkuId);
var hashToken = await FlashSalePlanHasher.HashAsync(eventData.Plan.LastModificationTime, product.LastModificationTime, productSku.LastModificationTime);
if (hashToken != eventData.HashToken)
{
await DistributedEventBus.PublishAsync(new CreateFlashSaleOrderCompleteEto()
{
TenantId = eventData.TenantId,
PlanId = eventData.PlanId,
OrderId = null,
UserId = eventData.UserId,
StoreId = eventData.StoreId,
PendingResultId = eventData.PendingResultId,
Success = false,
Reason = FlashSaleResultFailedReason.PreOrderExipred
});
return;
}
if (!await ProductManager.TryReduceInventoryAsync(product, productSku, 1, true))
{
await DistributedEventBus.PublishAsync(new CreateFlashSaleOrderCompleteEto()
{
TenantId = eventData.TenantId,
PlanId = eventData.PlanId,
OrderId = null,
UserId = eventData.UserId,
StoreId = eventData.StoreId,
PendingResultId = eventData.PendingResultId,
Success = false,
Reason = FlashSaleResultFailedReason.InsufficientInventory
});
return;
}
var productDetailId = productSku.ProductDetailId ?? product.ProductDetailId;
var productDetailEto = productDetailId.HasValue ?
ObjectMapper.Map<ProductDetail, FlashSaleProductDetailEto>(await ProductDetailRepository.GetAsync(productDetailId.Value)) :
null;
var productEto = ObjectMapper.Map<Product, FlashSaleProductEto>(product);
var createFlashSalesOrderEto = new CreateFlashSaleOrderEto()
{
TenantId = eventData.TenantId,
PlanId = eventData.PlanId,
UserId = eventData.UserId,
PendingResultId = eventData.PendingResultId,
StoreId = eventData.StoreId,
CreateTime = eventData.CreateTime,
CustomerRemark = eventData.CustomerRemark,
Product = productEto,
ProductDetail = productDetailEto,
Plan = eventData.Plan
};
foreach (var item in eventData.ExtraProperties)
{
createFlashSalesOrderEto.ExtraProperties.Add(item.Key, item.Value);
}
await DistributedEventBus.PublishAsync(createFlashSalesOrderEto);
}
}

12
plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/ILocalFlashSaleInventoryManager.cs

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using EasyAbp.Eshop.Products.Products;
namespace EasyAbp.EShop.Products.Products;
public interface ILocalFlashSaleInventoryManager : IFlashSaleInventoryManager
{
}

33
plugins/FlashSales/src/EasyAbp.EShop.Products.Plugins.FlashSales.Application/EasyAbp/EShop/Products/Products/LocalFlashSaleInventoryManager.cs

@ -0,0 +1,33 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using EasyAbp.EShop.Products.ProductInventories;
using Volo.Abp.DependencyInjection;
namespace EasyAbp.EShop.Products.Products;
public class LocalFlashSaleInventoryManager : ILocalFlashSaleInventoryManager, ITransientDependency
{
protected IProductInventoryProviderResolver ProductInventoryProviderResolver { get; }
public LocalFlashSaleInventoryManager(IProductInventoryProviderResolver productInventoryProviderResolver)
{
ProductInventoryProviderResolver = productInventoryProviderResolver;
}
public virtual async Task<bool> TryReduceInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId,
Guid productSkuId, int quantity, bool increaseSold)
{
var model = new InventoryQueryModel(tenantId, storeId, productId, productSkuId);
return await (await ProductInventoryProviderResolver.GetAsync(providerName))
.TryReduceInventoryAsync(model, quantity, increaseSold);
}
public virtual async Task<bool> TryIncreaseInventoryAsync(Guid? tenantId, string providerName, Guid storeId, Guid productId,
Guid productSkuId, int quantity, bool decreaseSold)
{
var model = new InventoryQueryModel(tenantId, storeId, productId, productSkuId);
return await (await ProductInventoryProviderResolver.GetAsync(providerName))
.TryIncreaseInventoryAsync(model, quantity, decreaseSold);
}
}
Loading…
Cancel
Save