mirror of https://github.com/EasyAbp/EShop.git
committed by
GitHub
37 changed files with 10900 additions and 31 deletions
@ -0,0 +1,40 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace EasyAbp.EShop.Products.Products.Dtos |
|||
{ |
|||
[Serializable] |
|||
public class ProductViewDto : ExtensibleCreationAuditedEntityDto<Guid> |
|||
{ |
|||
public Guid StoreId { get; set; } |
|||
|
|||
public string ProductGroupName { get; set; } |
|||
|
|||
public Guid ProductDetailId { get; set; } |
|||
|
|||
public string UniqueName { get; set; } |
|||
|
|||
public string DisplayName { get; set; } |
|||
|
|||
public InventoryStrategy InventoryStrategy { 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 decimal? MinimumPrice { get; set; } |
|||
|
|||
public decimal? MaximumPrice { get; set; } |
|||
|
|||
public long Sold { get; set; } |
|||
|
|||
public string ProductGroupDisplayName { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
using System; |
|||
using EasyAbp.EShop.Products.Products.Dtos; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace EasyAbp.EShop.Products.Products |
|||
{ |
|||
public interface IProductViewAppService : |
|||
IReadOnlyAppService< |
|||
ProductViewDto, |
|||
Guid, |
|||
GetProductListInput> |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,123 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Products.Products.CacheItems; |
|||
using EasyAbp.EShop.Products.Products.Dtos; |
|||
using EasyAbp.EShop.Products.Settings; |
|||
using Microsoft.Extensions.Caching.Distributed; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Application.Services; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Abp.Uow; |
|||
|
|||
namespace EasyAbp.EShop.Products.Products |
|||
{ |
|||
public class ProductViewAppService : ReadOnlyAppService<ProductView, ProductViewDto, Guid, GetProductListInput>, |
|||
IProductViewAppService |
|||
{ |
|||
protected override string GetPolicyName { get; set; } = null; |
|||
protected override string GetListPolicyName { get; set; } = null; |
|||
|
|||
private readonly IProductViewCacheKeyProvider _productViewCacheKeyProvider; |
|||
private readonly IDistributedCache<ProductViewCacheItem> _cache; |
|||
private readonly IProductAppService _productAppService; |
|||
private readonly IProductViewRepository _repository; |
|||
|
|||
public ProductViewAppService( |
|||
IProductViewCacheKeyProvider productViewCacheKeyProvider, |
|||
IDistributedCache<ProductViewCacheItem> cache, |
|||
IProductAppService productAppService, |
|||
IProductViewRepository repository) : base(repository) |
|||
{ |
|||
_productViewCacheKeyProvider = productViewCacheKeyProvider; |
|||
_cache = cache; |
|||
_productAppService = productAppService; |
|||
_repository = repository; |
|||
} |
|||
|
|||
protected override IQueryable<ProductView> CreateFilteredQuery(GetProductListInput input) |
|||
{ |
|||
var query = input.CategoryId.HasValue |
|||
? _repository.WithDetails(input.CategoryId.Value) |
|||
: _repository.WithDetails(); |
|||
|
|||
return query |
|||
.Where(x => x.StoreId == input.StoreId) |
|||
.WhereIf(!input.ShowHidden, x => !x.IsHidden); |
|||
} |
|||
|
|||
public override async Task<PagedResultDto<ProductViewDto>> GetListAsync(GetProductListInput input) |
|||
{ |
|||
await CheckGetListPolicyAsync(); |
|||
|
|||
if (await _cache.GetAsync(await GetCacheKeyAsync(input.StoreId), true) == null) |
|||
{ |
|||
await BuildStoreProductViewsAsync(input.StoreId); |
|||
} |
|||
|
|||
var query = CreateFilteredQuery(input); |
|||
|
|||
var totalCount = await AsyncExecuter.CountAsync(query); |
|||
|
|||
query = ApplySorting(query, input); |
|||
query = ApplyPaging(query, input); |
|||
|
|||
var productViews = await AsyncExecuter.ToListAsync(query); |
|||
var entityDtos = await MapToGetListOutputDtosAsync(productViews); |
|||
|
|||
return new PagedResultDto<ProductViewDto>( |
|||
totalCount, |
|||
entityDtos |
|||
); |
|||
} |
|||
|
|||
protected virtual async Task<string> GetCacheKeyAsync(Guid storeId) |
|||
{ |
|||
return await _productViewCacheKeyProvider.GetCacheKeyAsync(storeId); |
|||
} |
|||
|
|||
public override async Task<ProductViewDto> GetAsync(Guid id) |
|||
{ |
|||
await CheckGetPolicyAsync(); |
|||
|
|||
var productView = await GetEntityByIdAsync(id); |
|||
|
|||
if (await _cache.GetAsync(await GetCacheKeyAsync(productView.StoreId), true) != null) |
|||
{ |
|||
return await base.GetAsync(id); |
|||
} |
|||
|
|||
await BuildStoreProductViewsAsync(productView.StoreId); |
|||
|
|||
productView = await GetEntityByIdAsync(id); |
|||
|
|||
return await MapToGetOutputDtoAsync(productView); |
|||
} |
|||
|
|||
protected virtual async Task BuildStoreProductViewsAsync(Guid storeId) |
|||
{ |
|||
var resultDto = await _productAppService.GetListAsync(new GetProductListInput |
|||
{ |
|||
StoreId = storeId |
|||
}); |
|||
|
|||
using var uow = UnitOfWorkManager.Begin(true, true); |
|||
|
|||
await _repository.DeleteAsync(x => x.StoreId == storeId, true); |
|||
|
|||
foreach (var product in resultDto.Items) |
|||
{ |
|||
await _repository.InsertAsync(ObjectMapper.Map<ProductDto, ProductView>(product)); |
|||
} |
|||
|
|||
await uow.CompleteAsync(); |
|||
|
|||
await _cache.SetAsync(await GetCacheKeyAsync(storeId), new ProductViewCacheItem(), |
|||
new DistributedCacheEntryOptions |
|||
{ |
|||
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(Convert.ToInt32( |
|||
await SettingProvider.GetOrNullAsync(ProductsSettings.ProductView.CacheDurationSeconds))) |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace EasyAbp.EShop.Products.Products.CacheItems |
|||
{ |
|||
public interface IProductViewCacheKeyProvider |
|||
{ |
|||
Task<string> GetCacheKeyAsync(Guid storeId); |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace EasyAbp.EShop.Products.Products.CacheItems |
|||
{ |
|||
public class ProductViewCacheItem |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace EasyAbp.EShop.Products.Products.CacheItems |
|||
{ |
|||
public class ProductViewCacheKeyProvider : IProductViewCacheKeyProvider, ITransientDependency |
|||
{ |
|||
public virtual Task<string> GetCacheKeyAsync(Guid storeId) |
|||
{ |
|||
return Task.FromResult(storeId.ToString()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace EasyAbp.EShop.Products.Products |
|||
{ |
|||
public interface IProductViewRepository : IRepository<ProductView, Guid> |
|||
{ |
|||
IQueryable<ProductView> GetQueryable(Guid categoryId); |
|||
|
|||
IQueryable<ProductView> WithDetails(Guid categoryId); |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace EasyAbp.EShop.Products.Products |
|||
{ |
|||
public interface IProductViewSoldDataUpdater |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,88 @@ |
|||
using System; |
|||
using Volo.Abp.Domain.Entities.Auditing; |
|||
|
|||
namespace EasyAbp.EShop.Products.Products |
|||
{ |
|||
public class ProductView : CreationAuditedAggregateRoot<Guid>, IProduct |
|||
{ |
|||
#region Properties of IProduct
|
|||
|
|||
public virtual Guid StoreId { get; protected set; } |
|||
|
|||
public virtual string ProductGroupName { get; protected set; } |
|||
|
|||
public virtual Guid ProductDetailId { get; protected set; } |
|||
|
|||
public virtual string UniqueName { get; protected set; } |
|||
|
|||
public virtual string DisplayName { get; protected set; } |
|||
|
|||
public virtual InventoryStrategy InventoryStrategy { get; protected set; } |
|||
|
|||
public virtual string MediaResources { get; protected set; } |
|||
|
|||
public virtual int DisplayOrder { get; protected set; } |
|||
|
|||
public virtual bool IsPublished { get; protected set; } |
|||
|
|||
public virtual bool IsStatic { get; protected set; } |
|||
|
|||
public virtual bool IsHidden { get; protected set; } |
|||
|
|||
#endregion
|
|||
|
|||
public virtual string ProductGroupDisplayName { get; protected set; } |
|||
|
|||
public virtual decimal? MinimumPrice { get; protected set; } |
|||
|
|||
public virtual decimal? MaximumPrice { get; protected set; } |
|||
|
|||
public virtual long Sold { get; protected set; } |
|||
|
|||
protected ProductView() |
|||
{ |
|||
} |
|||
|
|||
public ProductView( |
|||
Guid id, |
|||
Guid storeId, |
|||
string productGroupName, |
|||
Guid productDetailId, |
|||
string uniqueName, |
|||
string displayName, |
|||
InventoryStrategy inventoryStrategy, |
|||
bool isPublished, |
|||
bool isStatic, |
|||
bool isHidden, |
|||
string mediaResources, |
|||
int displayOrder, |
|||
string productGroupDisplayName, |
|||
decimal? minimumPrice, |
|||
decimal? maximumPrice, |
|||
long sold |
|||
) : base(id) |
|||
{ |
|||
StoreId = storeId; |
|||
ProductGroupName = productGroupName; |
|||
ProductDetailId = productDetailId; |
|||
UniqueName = uniqueName?.Trim(); |
|||
DisplayName = displayName; |
|||
InventoryStrategy = inventoryStrategy; |
|||
IsPublished = isPublished; |
|||
IsStatic = isStatic; |
|||
IsHidden = isHidden; |
|||
MediaResources = mediaResources; |
|||
DisplayOrder = displayOrder; |
|||
|
|||
ProductGroupDisplayName = productGroupDisplayName; |
|||
MinimumPrice = minimumPrice; |
|||
MaximumPrice = maximumPrice; |
|||
Sold = sold; |
|||
} |
|||
|
|||
public void SetSold(long sold) |
|||
{ |
|||
Sold = sold; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using EasyAbp.EShop.Products.ProductInventories; |
|||
using EasyAbp.EShop.Products.Products.CacheItems; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
|
|||
namespace EasyAbp.EShop.Products.Products |
|||
{ |
|||
public class ProductViewSoldDataUpdater : IDistributedEventHandler<ProductInventoryChangedEto>, |
|||
IProductViewSoldDataUpdater, ITransientDependency |
|||
{ |
|||
private readonly IProductViewCacheKeyProvider _productViewCacheKeyProvider; |
|||
private readonly IDistributedCache<ProductViewCacheItem> _cache; |
|||
|
|||
public ProductViewSoldDataUpdater( |
|||
IProductViewCacheKeyProvider productViewCacheKeyProvider, |
|||
IDistributedCache<ProductViewCacheItem> cache) |
|||
{ |
|||
_productViewCacheKeyProvider = productViewCacheKeyProvider; |
|||
_cache = cache; |
|||
} |
|||
|
|||
public async Task HandleEventAsync(ProductInventoryChangedEto eventData) |
|||
{ |
|||
await ClearProductViewCacheAsync(eventData.StoreId); |
|||
} |
|||
|
|||
protected virtual async Task ClearProductViewCacheAsync(Guid storeId) |
|||
{ |
|||
await _cache.RemoveAsync(await _productViewCacheKeyProvider.GetCacheKeyAsync(storeId)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using System.Linq; |
|||
using Microsoft.EntityFrameworkCore; |
|||
|
|||
namespace EasyAbp.EShop.Products.Products |
|||
{ |
|||
public static class ProductViewEfCoreQueryableExtensions |
|||
{ |
|||
public static IQueryable<ProductView> IncludeDetails(this IQueryable<ProductView> queryable, bool include = true) |
|||
{ |
|||
if (!include) |
|||
{ |
|||
return queryable; |
|||
} |
|||
|
|||
return queryable |
|||
// .Include(x => x.xxx) // TODO: AbpHelper generated
|
|||
; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using EasyAbp.EShop.Products.EntityFrameworkCore; |
|||
using Volo.Abp.Domain.Repositories.EntityFrameworkCore; |
|||
using Volo.Abp.EntityFrameworkCore; |
|||
|
|||
namespace EasyAbp.EShop.Products.Products |
|||
{ |
|||
public class ProductViewRepository : EfCoreRepository<IProductsDbContext, ProductView, Guid>, IProductViewRepository |
|||
{ |
|||
public ProductViewRepository(IDbContextProvider<IProductsDbContext> dbContextProvider) : base(dbContextProvider) |
|||
{ |
|||
} |
|||
|
|||
public override IQueryable<ProductView> WithDetails() |
|||
{ |
|||
return base.GetQueryable().IncludeDetails(); |
|||
} |
|||
|
|||
public IQueryable<ProductView> GetQueryable(Guid categoryId) |
|||
{ |
|||
return JoinProductCategories(DbSet, categoryId); |
|||
} |
|||
|
|||
public IQueryable<ProductView> WithDetails(Guid categoryId) |
|||
{ |
|||
return JoinProductCategories(WithDetails(), categoryId); |
|||
} |
|||
|
|||
protected virtual IQueryable<ProductView> JoinProductCategories(IQueryable<ProductView> queryable, Guid categoryId) |
|||
{ |
|||
return queryable.Join( |
|||
DbContext.ProductCategories.Where(productCategory => productCategory.CategoryId == categoryId), |
|||
product => product.Id, |
|||
productCategory => productCategory.ProductId, |
|||
(product, productCategory) => product |
|||
); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
using System; |
|||
using EasyAbp.EShop.Products.Products.Dtos; |
|||
using Volo.Abp.Application.Dtos; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace EasyAbp.EShop.Products.Products |
|||
{ |
|||
[RemoteService(Name = "EasyAbpEShopProducts")] |
|||
[Route("/api/e-shop/products/product/view")] |
|||
public class ProductViewController : ProductsController, IProductViewAppService |
|||
{ |
|||
private readonly IProductViewAppService _service; |
|||
|
|||
public ProductViewController(IProductViewAppService service) |
|||
{ |
|||
_service = service; |
|||
} |
|||
|
|||
[HttpGet] |
|||
public virtual Task<PagedResultDto<ProductViewDto>> GetListAsync(GetProductListInput input) |
|||
{ |
|||
return _service.GetListAsync(input); |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("{id}")] |
|||
public virtual Task<ProductViewDto> GetAsync(Guid id) |
|||
{ |
|||
return _service.GetAsync(id); |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,72 @@ |
|||
using System; |
|||
using Microsoft.EntityFrameworkCore.Migrations; |
|||
|
|||
namespace EShopSample.Migrations |
|||
{ |
|||
public partial class AddedProductView : Migration |
|||
{ |
|||
protected override void Up(MigrationBuilder migrationBuilder) |
|||
{ |
|||
migrationBuilder.DropIndex( |
|||
name: "IX_EasyAbpEShopProductsProducts_UniqueName", |
|||
table: "EasyAbpEShopProductsProducts"); |
|||
|
|||
migrationBuilder.CreateTable( |
|||
name: "EasyAbpEShopProductsProductViews", |
|||
columns: table => new |
|||
{ |
|||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false), |
|||
StoreId = table.Column<Guid>(type: "uniqueidentifier", nullable: false), |
|||
ProductGroupName = table.Column<string>(type: "nvarchar(max)", nullable: true), |
|||
ProductDetailId = table.Column<Guid>(type: "uniqueidentifier", nullable: false), |
|||
UniqueName = table.Column<string>(type: "nvarchar(450)", nullable: true), |
|||
DisplayName = table.Column<string>(type: "nvarchar(max)", nullable: true), |
|||
InventoryStrategy = table.Column<int>(type: "int", nullable: false), |
|||
MediaResources = table.Column<string>(type: "nvarchar(max)", nullable: true), |
|||
DisplayOrder = table.Column<int>(type: "int", nullable: false), |
|||
IsPublished = table.Column<bool>(type: "bit", nullable: false), |
|||
IsStatic = table.Column<bool>(type: "bit", nullable: false), |
|||
IsHidden = table.Column<bool>(type: "bit", nullable: false), |
|||
ProductGroupDisplayName = table.Column<string>(type: "nvarchar(max)", nullable: true), |
|||
MinimumPrice = table.Column<decimal>(type: "decimal(18,2)", nullable: true), |
|||
MaximumPrice = table.Column<decimal>(type: "decimal(18,2)", nullable: true), |
|||
Sold = table.Column<long>(type: "bigint", nullable: false), |
|||
ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: true), |
|||
ConcurrencyStamp = table.Column<string>(type: "nvarchar(40)", maxLength: 40, nullable: true), |
|||
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false), |
|||
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true) |
|||
}, |
|||
constraints: table => |
|||
{ |
|||
table.PrimaryKey("PK_EasyAbpEShopProductsProductViews", x => x.Id); |
|||
}); |
|||
|
|||
migrationBuilder.CreateIndex( |
|||
name: "IX_EasyAbpEShopProductsProducts_UniqueName", |
|||
table: "EasyAbpEShopProductsProducts", |
|||
column: "UniqueName"); |
|||
|
|||
migrationBuilder.CreateIndex( |
|||
name: "IX_EasyAbpEShopProductsProductViews_UniqueName", |
|||
table: "EasyAbpEShopProductsProductViews", |
|||
column: "UniqueName"); |
|||
} |
|||
|
|||
protected override void Down(MigrationBuilder migrationBuilder) |
|||
{ |
|||
migrationBuilder.DropTable( |
|||
name: "EasyAbpEShopProductsProductViews"); |
|||
|
|||
migrationBuilder.DropIndex( |
|||
name: "IX_EasyAbpEShopProductsProducts_UniqueName", |
|||
table: "EasyAbpEShopProductsProducts"); |
|||
|
|||
migrationBuilder.CreateIndex( |
|||
name: "IX_EasyAbpEShopProductsProducts_UniqueName", |
|||
table: "EasyAbpEShopProductsProducts", |
|||
column: "UniqueName", |
|||
unique: true, |
|||
filter: "[UniqueName] IS NOT NULL"); |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,49 @@ |
|||
using Microsoft.EntityFrameworkCore.Migrations; |
|||
|
|||
namespace EShopSample.Migrations |
|||
{ |
|||
public partial class ConfiguredDecimalPropertiesInProductView : Migration |
|||
{ |
|||
protected override void Up(MigrationBuilder migrationBuilder) |
|||
{ |
|||
migrationBuilder.AlterColumn<decimal>( |
|||
name: "MinimumPrice", |
|||
table: "EasyAbpEShopProductsProductViews", |
|||
type: "decimal(20,8)", |
|||
nullable: true, |
|||
oldClrType: typeof(decimal), |
|||
oldType: "decimal(18,2)", |
|||
oldNullable: true); |
|||
|
|||
migrationBuilder.AlterColumn<decimal>( |
|||
name: "MaximumPrice", |
|||
table: "EasyAbpEShopProductsProductViews", |
|||
type: "decimal(20,8)", |
|||
nullable: true, |
|||
oldClrType: typeof(decimal), |
|||
oldType: "decimal(18,2)", |
|||
oldNullable: true); |
|||
} |
|||
|
|||
protected override void Down(MigrationBuilder migrationBuilder) |
|||
{ |
|||
migrationBuilder.AlterColumn<decimal>( |
|||
name: "MinimumPrice", |
|||
table: "EasyAbpEShopProductsProductViews", |
|||
type: "decimal(18,2)", |
|||
nullable: true, |
|||
oldClrType: typeof(decimal), |
|||
oldType: "decimal(20,8)", |
|||
oldNullable: true); |
|||
|
|||
migrationBuilder.AlterColumn<decimal>( |
|||
name: "MaximumPrice", |
|||
table: "EasyAbpEShopProductsProductViews", |
|||
type: "decimal(18,2)", |
|||
nullable: true, |
|||
oldClrType: typeof(decimal), |
|||
oldType: "decimal(20,8)", |
|||
oldNullable: true); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue