Browse Source

Clear the product view cache when product discounts begin or end

pull/252/head
gdlcf88 4 years ago
parent
commit
9856c49769
  1. 27
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/Products/ProductAppService.cs
  2. 67
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/Products/ProductViewAppService.cs
  3. 14
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductManager.cs
  4. 5
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/PriceDataModel.cs
  5. 6
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductDiscountContext.cs
  6. 5
      modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductManager.cs

27
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/Products/ProductAppService.cs

@ -82,7 +82,7 @@ namespace EasyAbp.EShop.Products.Products
var dto = await MapToGetOutputDtoAsync(product); var dto = await MapToGetOutputDtoAsync(product);
await LoadDtoExtraDataAsync(product, dto); await LoadDtoExtraDataAsync(product, dto, Clock.Now);
await LoadDtosProductGroupDisplayNameAsync(new[] { dto }); await LoadDtosProductGroupDisplayNameAsync(new[] { dto });
UnitOfWorkManager.Current.OnCompleted(async () => { await ClearProductViewCacheAsync(product.StoreId); }); UnitOfWorkManager.Current.OnCompleted(async () => { await ClearProductViewCacheAsync(product.StoreId); });
@ -116,7 +116,7 @@ namespace EasyAbp.EShop.Products.Products
var dto = await MapToGetOutputDtoAsync(product); var dto = await MapToGetOutputDtoAsync(product);
await LoadDtoExtraDataAsync(product, dto); await LoadDtoExtraDataAsync(product, dto, Clock.Now);
await LoadDtosProductGroupDisplayNameAsync(new[] { dto }); await LoadDtosProductGroupDisplayNameAsync(new[] { dto });
UnitOfWorkManager.Current.OnCompleted(async () => { await ClearProductViewCacheAsync(product.StoreId); }); UnitOfWorkManager.Current.OnCompleted(async () => { await ClearProductViewCacheAsync(product.StoreId); });
@ -201,7 +201,7 @@ namespace EasyAbp.EShop.Products.Products
var dto = await MapToGetOutputDtoAsync(product); var dto = await MapToGetOutputDtoAsync(product);
await LoadDtoExtraDataAsync(product, dto); await LoadDtoExtraDataAsync(product, dto, Clock.Now);
await LoadDtosProductGroupDisplayNameAsync(new[] { dto }); await LoadDtosProductGroupDisplayNameAsync(new[] { dto });
return dto; return dto;
@ -232,7 +232,7 @@ namespace EasyAbp.EShop.Products.Products
var dto = await MapToGetOutputDtoAsync(product); var dto = await MapToGetOutputDtoAsync(product);
await LoadDtoExtraDataAsync(product, dto); await LoadDtoExtraDataAsync(product, dto, Clock.Now);
await LoadDtosProductGroupDisplayNameAsync(new[] { dto }); await LoadDtosProductGroupDisplayNameAsync(new[] { dto });
return dto; return dto;
@ -257,13 +257,14 @@ namespace EasyAbp.EShop.Products.Products
var products = await AsyncExecuter.ToListAsync(query); var products = await AsyncExecuter.ToListAsync(query);
var now = Clock.Now;
var items = new List<ProductDto>(); var items = new List<ProductDto>();
foreach (var product in products) foreach (var product in products)
{ {
var productDto = await MapToGetListOutputDtoAsync(product); var productDto = await MapToGetListOutputDtoAsync(product);
await LoadDtoExtraDataAsync(product, productDto); await LoadDtoExtraDataAsync(product, productDto, now);
items.Add(productDto); items.Add(productDto);
} }
@ -296,21 +297,23 @@ namespace EasyAbp.EShop.Products.Products
return productDto; return productDto;
} }
protected virtual async Task<ProductDto> LoadDtoExtraDataAsync(Product product, ProductDto productDto) protected virtual async Task<ProductDto> LoadDtoExtraDataAsync(Product product, ProductDto productDto,
DateTime now)
{ {
await LoadDtoInventoryDataAsync(product, productDto); await LoadDtoInventoryDataAsync(product, productDto);
await LoadDtoPriceDataAsync(product, productDto); await LoadDtoPriceDataAsync(product, productDto, now);
return productDto; return productDto;
} }
protected virtual async Task<ProductDto> LoadDtoPriceDataAsync(Product product, ProductDto productDto) protected virtual async Task<ProductDto> LoadDtoPriceDataAsync(Product product, ProductDto productDto,
DateTime now)
{ {
foreach (var productSku in product.ProductSkus) foreach (var productSku in product.ProductSkus)
{ {
var productSkuDto = productDto.ProductSkus.First(x => x.Id == productSku.Id); var productSkuDto = productDto.ProductSkus.First(x => x.Id == productSku.Id);
var priceDataModel = await _productManager.GetRealPriceAsync(product, productSku); var priceDataModel = await _productManager.GetRealPriceAsync(product, productSku, now);
productSkuDto.PriceWithoutDiscount = priceDataModel.PriceWithoutDiscount; productSkuDto.PriceWithoutDiscount = priceDataModel.PriceWithoutDiscount;
productSkuDto.Price = priceDataModel.DiscountedPrice; productSkuDto.Price = priceDataModel.DiscountedPrice;
@ -364,7 +367,7 @@ namespace EasyAbp.EShop.Products.Products
var dto = await MapToGetOutputDtoAsync(product); var dto = await MapToGetOutputDtoAsync(product);
await LoadDtoExtraDataAsync(product, dto); await LoadDtoExtraDataAsync(product, dto, Clock.Now);
await LoadDtosProductGroupDisplayNameAsync(new[] { dto }); await LoadDtosProductGroupDisplayNameAsync(new[] { dto });
UnitOfWorkManager.Current.OnCompleted(async () => { await ClearProductViewCacheAsync(product.StoreId); }); UnitOfWorkManager.Current.OnCompleted(async () => { await ClearProductViewCacheAsync(product.StoreId); });
@ -389,7 +392,7 @@ namespace EasyAbp.EShop.Products.Products
var dto = await MapToGetOutputDtoAsync(product); var dto = await MapToGetOutputDtoAsync(product);
await LoadDtoExtraDataAsync(product, dto); await LoadDtoExtraDataAsync(product, dto, Clock.Now);
await LoadDtosProductGroupDisplayNameAsync(new[] { dto }); await LoadDtosProductGroupDisplayNameAsync(new[] { dto });
UnitOfWorkManager.Current.OnCompleted(async () => { await ClearProductViewCacheAsync(product.StoreId); }); UnitOfWorkManager.Current.OnCompleted(async () => { await ClearProductViewCacheAsync(product.StoreId); });
@ -411,7 +414,7 @@ namespace EasyAbp.EShop.Products.Products
var dto = await MapToGetOutputDtoAsync(product); var dto = await MapToGetOutputDtoAsync(product);
await LoadDtoExtraDataAsync(product, dto); await LoadDtoExtraDataAsync(product, dto, Clock.Now);
await LoadDtosProductGroupDisplayNameAsync(new[] { dto }); await LoadDtosProductGroupDisplayNameAsync(new[] { dto });
UnitOfWorkManager.Current.OnCompleted(async () => { await ClearProductViewCacheAsync(product.StoreId); }); UnitOfWorkManager.Current.OnCompleted(async () => { await ClearProductViewCacheAsync(product.StoreId); });

67
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Application/EasyAbp/EShop/Products/Products/ProductViewAppService.cs

@ -117,31 +117,80 @@ namespace EasyAbp.EShop.Products.Products
{ {
var products = await _productRepository.GetListAsync(x => x.StoreId == storeId, true); var products = await _productRepository.GetListAsync(x => x.StoreId == storeId, true);
var now = Clock.Now;
using var uow = UnitOfWorkManager.Begin(true, true); using var uow = UnitOfWorkManager.Begin(true, true);
await _repository.DeleteAsync(x => x.StoreId == storeId, true); await _repository.DeleteAsync(x => x.StoreId == storeId, true);
var productViews = new List<ProductView>();
foreach (var product in products) foreach (var product in products)
{ {
var productView = ObjectMapper.Map<Product, ProductView>(product); var productView = ObjectMapper.Map<Product, ProductView>(product);
await FillPriceInfoWithRealPriceAsync(product, productView); await FillPriceInfoWithRealPriceAsync(product, productView, now);
await _repository.InsertAsync(productView); productViews.Add(await _repository.InsertAsync(productView));
} }
await uow.SaveChangesAsync(); await uow.SaveChangesAsync();
await uow.CompleteAsync(); await uow.CompleteAsync();
await _cache.SetAsync(await GetCacheKeyAsync(storeId), new ProductViewCacheItem(), var duration = await GetCacheDurationOrNullAsync(productViews, now);
new DistributedCacheEntryOptions
if (duration.HasValue)
{
await _cache.SetAsync(await GetCacheKeyAsync(storeId), new ProductViewCacheItem(),
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = duration
});
}
}
protected async Task<TimeSpan?> GetCacheDurationOrNullAsync(List<ProductView> productViews,
DateTime now)
{
// refresh the cache when a new discount takes effect or an old discount ends.
var minFromTime = productViews.SelectMany(x => x.ProductDiscounts)
.Where(x => x.FromTime.HasValue && x.FromTime > now).MinBy(x => x.FromTime)?.FromTime;
var minToTime = productViews.SelectMany(x => x.ProductDiscounts)
.Where(x => x.ToTime.HasValue && x.ToTime > now).MinBy(x => x.ToTime)?.ToTime;
DateTime? nextTime;
if (minFromTime.HasValue)
{
if (minToTime.HasValue)
{
nextTime = minFromTime < minToTime ? minFromTime : minToTime;
}
else
{ {
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(Convert.ToInt32( nextTime = minFromTime;
await SettingProvider.GetOrNullAsync(ProductsSettings.ProductView.CacheDurationSeconds))) }
}); }
else
{
nextTime = minToTime;
}
// use `Clock.Now` to achieve higher timeliness.
var duration = nextTime.HasValue ? nextTime - Clock.Now : null;
if (duration <= TimeSpan.Zero)
{
return null;
}
var defaultDuration = TimeSpan.FromSeconds(Convert.ToInt32(
await SettingProvider.GetOrNullAsync(ProductsSettings.ProductView.CacheDurationSeconds)));
return duration.HasValue && duration.Value < defaultDuration ? duration : defaultDuration;
} }
protected virtual async Task FillPriceInfoWithRealPriceAsync(Product product, ProductView productView) protected virtual async Task FillPriceInfoWithRealPriceAsync(Product product, ProductView productView,
DateTime now)
{ {
if (product.ProductSkus.IsNullOrEmpty()) if (product.ProductSkus.IsNullOrEmpty())
{ {
@ -156,7 +205,7 @@ namespace EasyAbp.EShop.Products.Products
foreach (var productSku in product.ProductSkus) foreach (var productSku in product.ProductSkus)
{ {
var overrideProductDiscounts = false; var overrideProductDiscounts = false;
var priceDataModel = await _productManager.GetRealPriceAsync(product, productSku); var priceDataModel = await _productManager.GetRealPriceAsync(product, productSku, now);
if (min is null || priceDataModel.DiscountedPrice < min.Value) if (min is null || priceDataModel.DiscountedPrice < min.Value)
{ {

14
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/IProductManager.cs

@ -13,23 +13,23 @@ namespace EasyAbp.EShop.Products.Products
Task<Product> UpdateAsync(Product product, IEnumerable<Guid> categoryIds = null); Task<Product> UpdateAsync(Product product, IEnumerable<Guid> categoryIds = null);
Task DeleteAsync(Product product); Task DeleteAsync(Product product);
Task DeleteAsync(Guid id); Task DeleteAsync(Guid id);
Task<Product> CreateSkuAsync(Product product, ProductSku productSku); Task<Product> CreateSkuAsync(Product product, ProductSku productSku);
Task<Product> UpdateSkuAsync(Product product, ProductSku productSku); Task<Product> UpdateSkuAsync(Product product, ProductSku productSku);
Task<Product> DeleteSkuAsync(Product product, ProductSku productSku); Task<Product> DeleteSkuAsync(Product product, ProductSku productSku);
Task<bool> IsInventorySufficientAsync(Product product, ProductSku productSku, int quantity); Task<bool> IsInventorySufficientAsync(Product product, ProductSku productSku, int quantity);
Task<InventoryDataModel> GetInventoryDataAsync(Product product, ProductSku productSku); Task<InventoryDataModel> GetInventoryDataAsync(Product product, ProductSku productSku);
Task<bool> TryIncreaseInventoryAsync(Product product, ProductSku productSku, int quantity, bool reduceSold); Task<bool> TryIncreaseInventoryAsync(Product product, ProductSku productSku, int quantity, bool reduceSold);
Task<bool> TryReduceInventoryAsync(Product product, ProductSku productSku, int quantity, bool increaseSold); Task<bool> TryReduceInventoryAsync(Product product, ProductSku productSku, int quantity, bool increaseSold);
Task<PriceDataModel> GetRealPriceAsync(Product product, ProductSku productSku); Task<PriceDataModel> GetRealPriceAsync(Product product, ProductSku productSku, DateTime now);
} }
} }

5
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/PriceDataModel.cs

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Volo.Abp.Timing;
namespace EasyAbp.EShop.Products.Products; namespace EasyAbp.EShop.Products.Products;
@ -24,14 +23,14 @@ public class PriceDataModel : IHasFullDiscountsInfo
public List<OrderDiscountPreviewInfoModel> OrderDiscountPreviews { get; } = new(); public List<OrderDiscountPreviewInfoModel> OrderDiscountPreviews { get; } = new();
public PriceDataModel(decimal priceWithoutDiscount, IClock clock) public PriceDataModel(decimal priceWithoutDiscount, DateTime now)
{ {
if (PriceWithoutDiscount < decimal.Zero) if (PriceWithoutDiscount < decimal.Zero)
{ {
throw new OverflowException(); throw new OverflowException();
} }
Now = clock.Now; Now = now;
PriceWithoutDiscount = priceWithoutDiscount; PriceWithoutDiscount = priceWithoutDiscount;
} }
} }

6
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductDiscountContext.cs

@ -1,4 +1,4 @@
using Volo.Abp.Timing; using System;
namespace EasyAbp.EShop.Products.Products; namespace EasyAbp.EShop.Products.Products;
@ -10,10 +10,10 @@ public class ProductDiscountContext
public PriceDataModel PriceDataModel { get; } public PriceDataModel PriceDataModel { get; }
public ProductDiscountContext(Product product, ProductSku productSku, decimal priceFromPriceProvider, IClock clock) public ProductDiscountContext(Product product, ProductSku productSku, decimal priceFromPriceProvider, DateTime now)
{ {
Product = product; Product = product;
ProductSku = productSku; ProductSku = productSku;
PriceDataModel = new PriceDataModel(priceFromPriceProvider, clock); PriceDataModel = new PriceDataModel(priceFromPriceProvider, now);
} }
} }

5
modules/EasyAbp.EShop.Products/src/EasyAbp.EShop.Products.Domain/EasyAbp/EShop/Products/Products/ProductManager.cs

@ -268,11 +268,12 @@ namespace EasyAbp.EShop.Products.Products
.TryReduceInventoryAsync(model, quantity, increaseSold, isFlashSale); .TryReduceInventoryAsync(model, quantity, increaseSold, isFlashSale);
} }
public virtual async Task<PriceDataModel> GetRealPriceAsync(Product product, ProductSku productSku) public virtual async Task<PriceDataModel> GetRealPriceAsync(Product product, ProductSku productSku,
DateTime now)
{ {
var price = await _productPriceProvider.GetPriceAsync(product, productSku); var price = await _productPriceProvider.GetPriceAsync(product, productSku);
var context = new ProductDiscountContext(product, productSku, price, Clock); var context = new ProductDiscountContext(product, productSku, price, now);
// Todo: provider execution ordering. // Todo: provider execution ordering.
foreach (var provider in LazyServiceProvider.LazyGetService<IEnumerable<IProductDiscountProvider>>()) foreach (var provider in LazyServiceProvider.LazyGetService<IEnumerable<IProductDiscountProvider>>())

Loading…
Cancel
Save