Browse Source

Merge pull request #252 from EasyAbp/product-view-cache

Clear the product view cache when product discounts begin or end
pull/254/head
Super 3 years ago
committed by GitHub
parent
commit
6e153b7537
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  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);
await LoadDtoExtraDataAsync(product, dto);
await LoadDtoExtraDataAsync(product, dto, Clock.Now);
await LoadDtosProductGroupDisplayNameAsync(new[] { dto });
UnitOfWorkManager.Current.OnCompleted(async () => { await ClearProductViewCacheAsync(product.StoreId); });
@ -116,7 +116,7 @@ namespace EasyAbp.EShop.Products.Products
var dto = await MapToGetOutputDtoAsync(product);
await LoadDtoExtraDataAsync(product, dto);
await LoadDtoExtraDataAsync(product, dto, Clock.Now);
await LoadDtosProductGroupDisplayNameAsync(new[] { dto });
UnitOfWorkManager.Current.OnCompleted(async () => { await ClearProductViewCacheAsync(product.StoreId); });
@ -201,7 +201,7 @@ namespace EasyAbp.EShop.Products.Products
var dto = await MapToGetOutputDtoAsync(product);
await LoadDtoExtraDataAsync(product, dto);
await LoadDtoExtraDataAsync(product, dto, Clock.Now);
await LoadDtosProductGroupDisplayNameAsync(new[] { dto });
return dto;
@ -232,7 +232,7 @@ namespace EasyAbp.EShop.Products.Products
var dto = await MapToGetOutputDtoAsync(product);
await LoadDtoExtraDataAsync(product, dto);
await LoadDtoExtraDataAsync(product, dto, Clock.Now);
await LoadDtosProductGroupDisplayNameAsync(new[] { dto });
return dto;
@ -257,13 +257,14 @@ namespace EasyAbp.EShop.Products.Products
var products = await AsyncExecuter.ToListAsync(query);
var now = Clock.Now;
var items = new List<ProductDto>();
foreach (var product in products)
{
var productDto = await MapToGetListOutputDtoAsync(product);
await LoadDtoExtraDataAsync(product, productDto);
await LoadDtoExtraDataAsync(product, productDto, now);
items.Add(productDto);
}
@ -296,21 +297,23 @@ namespace EasyAbp.EShop.Products.Products
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 LoadDtoPriceDataAsync(product, productDto);
await LoadDtoPriceDataAsync(product, productDto, now);
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)
{
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.Price = priceDataModel.DiscountedPrice;
@ -364,7 +367,7 @@ namespace EasyAbp.EShop.Products.Products
var dto = await MapToGetOutputDtoAsync(product);
await LoadDtoExtraDataAsync(product, dto);
await LoadDtoExtraDataAsync(product, dto, Clock.Now);
await LoadDtosProductGroupDisplayNameAsync(new[] { dto });
UnitOfWorkManager.Current.OnCompleted(async () => { await ClearProductViewCacheAsync(product.StoreId); });
@ -389,7 +392,7 @@ namespace EasyAbp.EShop.Products.Products
var dto = await MapToGetOutputDtoAsync(product);
await LoadDtoExtraDataAsync(product, dto);
await LoadDtoExtraDataAsync(product, dto, Clock.Now);
await LoadDtosProductGroupDisplayNameAsync(new[] { dto });
UnitOfWorkManager.Current.OnCompleted(async () => { await ClearProductViewCacheAsync(product.StoreId); });
@ -411,7 +414,7 @@ namespace EasyAbp.EShop.Products.Products
var dto = await MapToGetOutputDtoAsync(product);
await LoadDtoExtraDataAsync(product, dto);
await LoadDtoExtraDataAsync(product, dto, Clock.Now);
await LoadDtosProductGroupDisplayNameAsync(new[] { dto });
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 now = Clock.Now;
using var uow = UnitOfWorkManager.Begin(true, true);
await _repository.DeleteAsync(x => x.StoreId == storeId, true);
var productViews = new List<ProductView>();
foreach (var product in products)
{
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.CompleteAsync();
await _cache.SetAsync(await GetCacheKeyAsync(storeId), new ProductViewCacheItem(),
new DistributedCacheEntryOptions
var duration = await GetCacheDurationOrNullAsync(productViews, now);
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(
await SettingProvider.GetOrNullAsync(ProductsSettings.ProductView.CacheDurationSeconds)))
});
nextTime = minFromTime;
}
}
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())
{
@ -156,7 +205,7 @@ namespace EasyAbp.EShop.Products.Products
foreach (var productSku in product.ProductSkus)
{
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)
{

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 DeleteAsync(Product product);
Task DeleteAsync(Guid id);
Task<Product> CreateSkuAsync(Product product, ProductSku productSku);
Task<Product> UpdateSkuAsync(Product product, ProductSku productSku);
Task<Product> DeleteSkuAsync(Product product, ProductSku productSku);
Task<bool> IsInventorySufficientAsync(Product product, ProductSku productSku, int quantity);
Task<InventoryDataModel> GetInventoryDataAsync(Product product, ProductSku productSku);
Task<bool> TryIncreaseInventoryAsync(Product product, ProductSku productSku, int quantity, bool reduceSold);
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.Collections.Generic;
using Volo.Abp.Timing;
namespace EasyAbp.EShop.Products.Products;
@ -24,14 +23,14 @@ public class PriceDataModel : IHasFullDiscountsInfo
public List<OrderDiscountPreviewInfoModel> OrderDiscountPreviews { get; } = new();
public PriceDataModel(decimal priceWithoutDiscount, IClock clock)
public PriceDataModel(decimal priceWithoutDiscount, DateTime now)
{
if (PriceWithoutDiscount < decimal.Zero)
{
throw new OverflowException();
}
Now = clock.Now;
Now = now;
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;
@ -10,10 +10,10 @@ public class ProductDiscountContext
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;
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);
}
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 context = new ProductDiscountContext(product, productSku, price, Clock);
var context = new ProductDiscountContext(product, productSku, price, now);
// Todo: provider execution ordering.
foreach (var provider in LazyServiceProvider.LazyGetService<IEnumerable<IProductDiscountProvider>>())

Loading…
Cancel
Save