mirror of https://github.com/EasyAbp/EShop.git
32 changed files with 441 additions and 129 deletions
33
plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/EShopOrdersPluginsFlashSalesApplicationAutoMapperProfile.cs → plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Orders/Plugins/FlashSales/EShopOrdersPluginsFlashSalesApplicationAutoMapperProfile.cs
33
plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/EShopOrdersPluginsFlashSalesApplicationAutoMapperProfile.cs → plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Orders/Plugins/FlashSales/EShopOrdersPluginsFlashSalesApplicationAutoMapperProfile.cs
@ -1,34 +1,35 @@ |
|||
using System.Collections.Generic; |
|||
using System; |
|||
using AutoMapper; |
|||
using AutoMapper; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSalesPlans; |
|||
using EasyAbp.EShop.Products.ProductDetails.Dtos; |
|||
using EasyAbp.EShop.Products.Products; |
|||
using EasyAbp.EShop.Products.Products.Dtos; |
|||
using Volo.Abp.AutoMapper; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Json; |
|||
|
|||
namespace EasyAbp.EShop.Orders |
|||
namespace EasyAbp.EShop.Orders.Plugins.FlashSales |
|||
{ |
|||
public class EShopOrdersPluginsFlashSalesApplicationAutoMapperProfile : Profile, ISingletonDependency |
|||
public class EShopOrdersPluginsFlashSalesApplicationAutoMapperProfile : Profile |
|||
{ |
|||
public EShopOrdersPluginsFlashSalesApplicationAutoMapperProfile(IJsonSerializer jsonSerializer) |
|||
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<FlashSalesProductEto, ProductDto>(MemberList.Source) |
|||
.ForSourceMember(x => x.TenantId, opt => opt.DoNotValidate()) |
|||
CreateMap<FlashSalesProductEto, ProductDto>(MemberList.Destination) |
|||
.Ignore(dto => dto.Sold) |
|||
.Ignore(dto => dto.MinimumPrice) |
|||
.Ignore(dto => dto.MaximumPrice) |
|||
.MapExtraProperties(); |
|||
CreateMap<FlashSalesProductSkuEto, ProductSkuDto>(MemberList.Source) |
|||
.ForMember(x => x.AttributeOptionIds, opt => opt.Ignore()) |
|||
.AfterMap((src, dest) => dest.AttributeOptionIds = jsonSerializer.Deserialize<List<Guid>>(src.SerializedAttributeOptionIds)) |
|||
CreateMap<FlashSalesProductSkuEto, ProductSkuDto>(MemberList.Destination) |
|||
.ForSourceMember(entity => entity.SerializedAttributeOptionIds, opt => opt.DoNotValidate()) |
|||
.Ignore(dto => dto.DiscountedPrice) |
|||
.Ignore(dto => dto.Inventory) |
|||
.Ignore(dto => dto.Sold) |
|||
.MapExtraProperties(); |
|||
CreateMap<FlashSalesProductAttributeEto, ProductAttributeDto>(MemberList.Source) |
|||
CreateMap<FlashSalesProductAttributeEto, ProductAttributeDto>(MemberList.Destination) |
|||
.MapExtraProperties(); |
|||
CreateMap<FlashSalesProductAttributeOptionEto, ProductAttributeOptionDto>(MemberList.Source) |
|||
CreateMap<FlashSalesProductAttributeOptionEto, ProductAttributeOptionDto>(MemberList.Destination) |
|||
.MapExtraProperties(); |
|||
CreateMap<FlashSalesProductDetailEto, ProductDetailDto>(MemberList.Source) |
|||
CreateMap<FlashSalesProductDetailEto, ProductDetailDto>(MemberList.Destination) |
|||
.MapExtraProperties(); |
|||
} |
|||
} |
|||
16
plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/EShopOrdersPluginsFlashSalesApplicationModule.cs → plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Orders/Plugins/FlashSales/EShopOrdersPluginsFlashSalesApplicationModule.cs
16
plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Plugins/FlashSales/EShopOrdersPluginsFlashSalesApplicationModule.cs → plugins/FlashSales/src/EasyAbp.EShop.Orders.Plugins.FlashSales.Application/EasyAbp/EShop/Orders/Plugins/FlashSales/EShopOrdersPluginsFlashSalesApplicationModule.cs
@ -1,48 +1,28 @@ |
|||
using System.Linq; |
|||
using AutoMapper; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSalesPlans; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSalesPlans.Dtos; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSalesResults; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSalesResults.Dtos; |
|||
using EasyAbp.EShop.Products.ProductDetails.Dtos; |
|||
using EasyAbp.EShop.Products.Products.Dtos; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Json; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales; |
|||
|
|||
public class FlashSalesApplicationAutoMapperProfile : Profile, ISingletonDependency |
|||
public class FlashSalesApplicationAutoMapperProfile : Profile |
|||
{ |
|||
public FlashSalesApplicationAutoMapperProfile(IJsonSerializer jsonSerializer) |
|||
public FlashSalesApplicationAutoMapperProfile() |
|||
{ |
|||
/* You can configure your AutoMapper mapping configuration here. |
|||
* Alternatively, you can split your mapping configurations |
|||
* into multiple profile classes for a better organization. */ |
|||
CreateMap<FlashSalesPlan, FlashSalesPlanDto>() |
|||
.MapExtraProperties(); |
|||
CreateMap<FlashSalesPlanDto, FlashSalesPlanEto>() |
|||
.ForMember(x => x.TenantId, opt => opt.Ignore()) |
|||
.MapExtraProperties(); |
|||
CreateMap<FlashSalesPlanCreateDto, FlashSalesPlan>(MemberList.Source); |
|||
CreateMap<FlashSalesPlanUpdateDto, FlashSalesPlan>(MemberList.Source); |
|||
CreateMap<FlashSalesPlan, FlashSalesPlanCacheItem>() |
|||
.MapExtraProperties(); |
|||
|
|||
CreateMap<FlashSalesResult, FlashSalesResultDto>() |
|||
CreateMap<FlashSalesPlanCacheItem, FlashSalesPlanEto>() |
|||
.MapExtraProperties(); |
|||
|
|||
CreateMap<ProductDto, FlashSalesProductEto>() |
|||
.ForMember(x => x.TenantId, opt => opt.Ignore()) |
|||
.MapExtraProperties(); |
|||
CreateMap<ProductSkuDto, FlashSalesProductSkuEto>() |
|||
.ForMember(x => x.SerializedAttributeOptionIds, opt => opt.Ignore()) |
|||
.AfterMap((src, dest) => dest.SerializedAttributeOptionIds = jsonSerializer.Serialize(src.AttributeOptionIds.OrderBy(x => x))) |
|||
.MapExtraProperties(); |
|||
CreateMap<ProductAttributeDto, FlashSalesProductAttributeEto>() |
|||
.MapExtraProperties(); |
|||
CreateMap<ProductAttributeOptionDto, FlashSalesProductAttributeOptionEto>() |
|||
.MapExtraProperties(); |
|||
CreateMap<ProductDetailDto, FlashSalesProductDetailEto>() |
|||
CreateMap<FlashSalesResult, FlashSalesResultDto>() |
|||
.MapExtraProperties(); |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,31 @@ |
|||
using System; |
|||
using System.Security.Cryptography; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalesPlans; |
|||
|
|||
public class FlashSalesPlanHasher : IFlashSalesPlanHasher, ITransientDependency |
|||
{ |
|||
public virtual Task<string> HashAsync(DateTime? planLastModificationTime, DateTime? productLastModificationTime, DateTime? productSkuLastModificationTime) |
|||
{ |
|||
var input = $"{planLastModificationTime}|{productLastModificationTime}|{productSkuLastModificationTime}"; |
|||
|
|||
return Task.FromResult(CreateMd5(input)); |
|||
} |
|||
|
|||
private static string CreateMd5(string input) |
|||
{ |
|||
using var md5 = MD5.Create(); |
|||
|
|||
var inputBytes = Encoding.UTF8.GetBytes(input); |
|||
|
|||
var sb = new StringBuilder(); |
|||
foreach (var t in md5.ComputeHash(inputBytes)) |
|||
{ |
|||
sb.Append(t.ToString("X2")); |
|||
} |
|||
return sb.ToString(); |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
using System; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.ObjectExtending; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalesPlans; |
|||
|
|||
[Serializable] |
|||
public class FlashSalesReduceInventoryEto : 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 int Quantity { get; set; } |
|||
|
|||
public FlashSalesPlanEto Plan { get; set; } |
|||
|
|||
public string HashToken { get; set; } |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalesPlans; |
|||
|
|||
public interface IFlashSalesPlanHasher |
|||
{ |
|||
Task<string> HashAsync(DateTime? planLastModificationTime, DateTime? productLastModificationTime, DateTime? productSkuLastModificationTime); |
|||
} |
|||
@ -1,9 +1,13 @@ |
|||
using System; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalesPlans; |
|||
|
|||
public class FlashSalesProductDetailEto : FullAuditedEntityEto<Guid> |
|||
[Serializable] |
|||
public class FlashSalesProductDetailEto : FullAuditedEntityEto<Guid>, IMultiTenant |
|||
{ |
|||
public Guid? TenantId { get; set; } |
|||
|
|||
public Guid? StoreId { get; set; } |
|||
|
|||
public string Description { get; set; } |
|||
|
|||
@ -0,0 +1,8 @@ |
|||
namespace EasyAbp.EShop.Plugins.FlashSales.FlashSalesResults; |
|||
|
|||
public static class FlashSalesResultFailedReason |
|||
{ |
|||
public const string InsufficientInventory = nameof(InsufficientInventory); |
|||
|
|||
public const string PreOrderExipred = nameof(PreOrderExipred); |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<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" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,37 @@ |
|||
using System.Linq; |
|||
using AutoMapper; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSalesPlans; |
|||
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, FlashSalesProductEto>() |
|||
.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, FlashSalesProductSkuEto>() |
|||
.ForMember(x => x.AttributeOptionIds, opt => opt.Ignore()) |
|||
.AfterMap(async (src, dest) => dest.AttributeOptionIds = (await attributeOptionIdsSerializer.DeserializeAsync(src.SerializedAttributeOptionIds)).ToList()) |
|||
.MapExtraProperties(); |
|||
CreateMap<ProductAttribute, FlashSalesProductAttributeEto>() |
|||
.MapExtraProperties(); |
|||
CreateMap<ProductAttributeOption, FlashSalesProductAttributeOptionEto>() |
|||
.MapExtraProperties(); |
|||
CreateMap<ProductDetail, FlashSalesProductDetailEto>() |
|||
.MapExtraProperties(); |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
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) |
|||
)] |
|||
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); |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,113 @@ |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSalesPlans; |
|||
using EasyAbp.EShop.Plugins.FlashSales.FlashSalesResults; |
|||
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 FlashSalesReduceInventoryEventHandler : IDistributedEventHandler<FlashSalesReduceInventoryEto>, ITransientDependency |
|||
{ |
|||
protected IProductRepository ProductRepository { get; } |
|||
|
|||
protected IProductDetailRepository ProductDetailRepository { get; } |
|||
|
|||
protected ProductManager ProductManager { get; } |
|||
|
|||
protected IDistributedEventBus DistributedEventBus { get; } |
|||
|
|||
protected IObjectMapper ObjectMapper { get; } |
|||
|
|||
protected IFlashSalesPlanHasher FlashSalesPlanHasher { get; } |
|||
|
|||
public FlashSalesReduceInventoryEventHandler( |
|||
IProductRepository productRepository, |
|||
IProductDetailRepository productDetailRepository, |
|||
ProductManager productManager, |
|||
IDistributedEventBus distributedEventBus, |
|||
IObjectMapper objectMapper, |
|||
IFlashSalesPlanHasher flashSalesPlanHasher) |
|||
{ |
|||
ProductRepository = productRepository; |
|||
ProductDetailRepository = productDetailRepository; |
|||
ProductManager = productManager; |
|||
DistributedEventBus = distributedEventBus; |
|||
ObjectMapper = objectMapper; |
|||
FlashSalesPlanHasher = flashSalesPlanHasher; |
|||
} |
|||
|
|||
[UnitOfWork] |
|||
public virtual async Task HandleEventAsync(FlashSalesReduceInventoryEto eventData) |
|||
{ |
|||
var product = await ProductRepository.GetAsync(eventData.Plan.ProductId); |
|||
var productSku = product.ProductSkus.Single(x => x.Id == eventData.Plan.ProductSkuId); |
|||
|
|||
var hashToken = await FlashSalesPlanHasher.HashAsync(eventData.Plan.LastModificationTime, product.LastModificationTime, productSku.LastModificationTime); |
|||
|
|||
if (hashToken != eventData.HashToken) |
|||
{ |
|||
await DistributedEventBus.PublishAsync(new CreateFlashSalesOrderCompleteEto() |
|||
{ |
|||
TenantId = eventData.TenantId, |
|||
PlanId = eventData.PlanId, |
|||
OrderId = null, |
|||
UserId = eventData.UserId, |
|||
StoreId = eventData.StoreId, |
|||
PendingResultId = eventData.PendingResultId, |
|||
Success = false, |
|||
Reason = FlashSalesResultFailedReason.PreOrderExipred |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
if (!await ProductManager.TryReduceInventoryAsync(product, productSku, eventData.Quantity, true)) |
|||
{ |
|||
await DistributedEventBus.PublishAsync(new CreateFlashSalesOrderCompleteEto() |
|||
{ |
|||
TenantId = eventData.TenantId, |
|||
PlanId = eventData.PlanId, |
|||
OrderId = null, |
|||
UserId = eventData.UserId, |
|||
StoreId = eventData.StoreId, |
|||
PendingResultId = eventData.PendingResultId, |
|||
Success = false, |
|||
Reason = FlashSalesResultFailedReason.InsufficientInventory |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
var productDetailId = productSku.ProductDetailId ?? product.ProductDetailId; |
|||
|
|||
var productDetailEto = productDetailId.HasValue ? |
|||
ObjectMapper.Map<ProductDetail, FlashSalesProductDetailEto>(await ProductDetailRepository.GetAsync(productDetailId.Value)) : |
|||
null; |
|||
|
|||
var productEto = ObjectMapper.Map<Product, FlashSalesProductEto>(product); |
|||
|
|||
var createFlashSalesOrderEto = new CreateFlashSalesOrderEto() |
|||
{ |
|||
TenantId = eventData.TenantId, |
|||
PlanId = eventData.PlanId, |
|||
UserId = eventData.UserId, |
|||
PendingResultId = eventData.PendingResultId, |
|||
StoreId = eventData.StoreId, |
|||
CreateTime = eventData.CreateTime, |
|||
CustomerRemark = eventData.CustomerRemark, |
|||
Quantity = eventData.Quantity, |
|||
Product = productEto, |
|||
ProductDetail = productDetailEto, |
|||
Plan = eventData.Plan |
|||
}; |
|||
|
|||
foreach (var item in eventData.ExtraProperties) |
|||
{ |
|||
createFlashSalesOrderEto.ExtraProperties.Add(item.Key, item.Value); |
|||
} |
|||
|
|||
await DistributedEventBus.PublishAsync(createFlashSalesOrderEto); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait /> |
|||
</Weavers> |
|||
@ -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> |
|||
Loading…
Reference in new issue