31 changed files with 625 additions and 43 deletions
@ -0,0 +1,13 @@ |
|||
(function(){ |
|||
$(function(){ |
|||
$('.product-list-item').click(function(){ |
|||
var $this = $(this); |
|||
var productId = $this.attr('data-product-id'); |
|||
eShopOnAbp.basketService.basket.addProduct({ |
|||
productId: productId |
|||
}).then(function(){ |
|||
abp.notify.success("Added product to your basket.", "Successfully added"); |
|||
}); |
|||
}); |
|||
}) |
|||
})(); |
|||
@ -0,0 +1,40 @@ |
|||
/* This file is automatically generated by ABP framework to use MVC Controllers from javascript. */ |
|||
|
|||
|
|||
// module basket
|
|||
|
|||
(function(){ |
|||
|
|||
// controller eShopOnAbp.basketService.basket
|
|||
|
|||
(function(){ |
|||
|
|||
abp.utils.createNamespace(window, 'eShopOnAbp.basketService.basket'); |
|||
|
|||
eShopOnAbp.basketService.basket.get = function(ajaxParams) { |
|||
return abp.ajax($.extend(true, { |
|||
url: abp.appPath + 'api/basket/basket', |
|||
type: 'GET' |
|||
}, ajaxParams)); |
|||
}; |
|||
|
|||
eShopOnAbp.basketService.basket.addProduct = function(input, ajaxParams) { |
|||
return abp.ajax($.extend(true, { |
|||
url: abp.appPath + 'api/basket/basket/product', |
|||
type: 'POST', |
|||
data: JSON.stringify(input) |
|||
}, ajaxParams)); |
|||
}; |
|||
|
|||
eShopOnAbp.basketService.basket.removeProduct = function(input, ajaxParams) { |
|||
return abp.ajax($.extend(true, { |
|||
url: abp.appPath + 'api/basket/basket/product' + abp.utils.buildQueryString([{ name: 'productId', value: input.productId }, { name: 'count', value: input.count }]) + '', |
|||
type: 'DELETE' |
|||
}, ajaxParams)); |
|||
}; |
|||
|
|||
})(); |
|||
|
|||
})(); |
|||
|
|||
|
|||
@ -0,0 +1,12 @@ |
|||
using System; |
|||
using System.ComponentModel.DataAnnotations; |
|||
|
|||
namespace EShopOnAbp.BasketService; |
|||
|
|||
public class AddProductDto |
|||
{ |
|||
public Guid ProductId { get; set; } |
|||
|
|||
[Range(1, int.MaxValue)] |
|||
public int Count { get; set; } = 1; |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace EShopOnAbp.BasketService; |
|||
|
|||
public class BasketDto |
|||
{ |
|||
public float TotalPrice { get; set; } |
|||
public List<BasketItemDto> Items { get; set; } |
|||
|
|||
public BasketDto() |
|||
{ |
|||
Items = new List<BasketItemDto>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System; |
|||
|
|||
namespace EShopOnAbp.BasketService; |
|||
|
|||
public class BasketItemDto |
|||
{ |
|||
public Guid ProductId { get; set; } |
|||
public string ProductName { get; set; } |
|||
public string ProductCode { get; set; } |
|||
public string ImageName { get; set; } |
|||
public int Count { get; set; } |
|||
public float TotalPrice { get; set; } |
|||
} |
|||
@ -1,8 +1,11 @@ |
|||
using Volo.Abp.Application.Services; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace EShopOnAbp.BasketService; |
|||
|
|||
public interface IBasketAppService : IApplicationService |
|||
{ |
|||
|
|||
Task<BasketDto> GetAsync(); |
|||
Task<BasketDto> AddProductAsync(AddProductDto input); |
|||
Task<BasketDto> RemoveProductAsync(RemoveProductDto input); |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using System; |
|||
|
|||
namespace EShopOnAbp.BasketService; |
|||
|
|||
public class RemoveProductDto |
|||
{ |
|||
public Guid ProductId { get; set; } |
|||
|
|||
public int? Count { get; set; } |
|||
} |
|||
@ -0,0 +1,82 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Volo.Abp.Users; |
|||
using EShopOnAbp.CatalogService.Products; |
|||
|
|||
namespace EShopOnAbp.BasketService; |
|||
|
|||
[Authorize] |
|||
public class BasketAppService : BasketServiceAppService, IBasketAppService |
|||
{ |
|||
private readonly IBasketRepository _basketRepository; |
|||
private readonly IBasketProductService _basketProductService; |
|||
|
|||
public BasketAppService( |
|||
IBasketRepository basketRepository, |
|||
IBasketProductService basketProductService) |
|||
{ |
|||
_basketRepository = basketRepository; |
|||
_basketProductService = basketProductService; |
|||
} |
|||
|
|||
public async Task<BasketDto> GetAsync() |
|||
{ |
|||
var basket = await _basketRepository.GetAsync(CurrentUser.GetId()); |
|||
return await GetBasketDtoAsync(basket); |
|||
} |
|||
|
|||
public async Task<BasketDto> AddProductAsync(AddProductDto input) |
|||
{ |
|||
var basket = await _basketRepository.GetAsync(CurrentUser.GetId()); |
|||
var product = await _basketProductService.GetAsync(input.ProductId); |
|||
|
|||
basket.AddProduct(product.Id); |
|||
|
|||
return await GetBasketDtoAsync(basket); |
|||
} |
|||
|
|||
public async Task<BasketDto> RemoveProductAsync(RemoveProductDto input) |
|||
{ |
|||
var basket = await _basketRepository.GetAsync(CurrentUser.GetId()); |
|||
var product = await _basketProductService.GetAsync(input.ProductId); |
|||
|
|||
basket.RemoveProduct(product.Id, input.Count); |
|||
|
|||
return await GetBasketDtoAsync(basket); |
|||
} |
|||
|
|||
private async Task<BasketDto> GetBasketDtoAsync(Basket basket) |
|||
{ |
|||
var products = new Dictionary<Guid, ProductDto>(); |
|||
|
|||
var basketDto = new BasketDto(); |
|||
|
|||
foreach (var basketItem in basket.Items) |
|||
{ |
|||
var basketItemDto = new BasketItemDto |
|||
{ |
|||
ProductId = basketItem.ProductId, |
|||
Count = basketItem.Count |
|||
}; |
|||
|
|||
var productDto = products.GetOrDefault(basketItem.ProductId); |
|||
if (productDto == null) |
|||
{ |
|||
productDto = await _basketProductService.GetAsync(basketItem.ProductId); |
|||
products[productDto.Id] = productDto; |
|||
} |
|||
|
|||
basketItemDto.ProductCode = productDto.Code; |
|||
basketItemDto.ImageName = productDto.ImageName; |
|||
basketItemDto.ProductName = productDto.Name; |
|||
basketItemDto.TotalPrice = productDto.Price * basketItemDto.Count; |
|||
} |
|||
|
|||
basketDto.TotalPrice = basketDto.Items.Sum(x => x.TotalPrice); |
|||
|
|||
return basketDto; |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using EShopOnAbp.CatalogService.Products; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace EShopOnAbp.BasketService; |
|||
|
|||
public class BasketProductService : IBasketProductService, ITransientDependency |
|||
{ |
|||
private readonly IProductAppService _productAppService; |
|||
private readonly IDistributedCache<ProductDto, Guid> _cache; |
|||
|
|||
public BasketProductService( |
|||
IProductAppService productAppService, |
|||
IDistributedCache<ProductDto, Guid> cache) |
|||
{ |
|||
_productAppService = productAppService; |
|||
_cache = cache; |
|||
} |
|||
|
|||
public async Task<ProductDto> GetAsync(Guid productId) |
|||
{ |
|||
return await _cache.GetOrAddAsync( |
|||
productId, |
|||
() => GetProductAsync(productId) |
|||
); |
|||
} |
|||
|
|||
private Task<ProductDto> GetProductAsync(Guid productId) |
|||
{ |
|||
return _productAppService.GetAsync(productId) ?? |
|||
throw new UserFriendlyException("Could not find the product!"); //TODO: Business exception with localization;
|
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using EShopOnAbp.CatalogService.Products; |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace EShopOnAbp.BasketService; |
|||
|
|||
public interface IBasketProductService |
|||
{ |
|||
[ItemNotNull] |
|||
Task<ProductDto> GetAsync(Guid productId); |
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Volo.Abp.Domain.Entities; |
|||
|
|||
namespace EShopOnAbp.BasketService; |
|||
|
|||
public class Basket : AggregateRoot<Guid> |
|||
{ |
|||
public List<BasketItem> Items { get; private set; } |
|||
|
|||
private Basket() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public Basket(Guid id) |
|||
: base(id) |
|||
{ |
|||
Items = new List<BasketItem>(); |
|||
} |
|||
|
|||
public void AddProduct(Guid productId, int count = 1) |
|||
{ |
|||
if (count < 1) |
|||
{ |
|||
throw new ArgumentOutOfRangeException(nameof(count), "Product count should be 1 or more!"); |
|||
} |
|||
|
|||
var item = Items.FirstOrDefault(x => x.ProductId == productId); |
|||
if (item == null) |
|||
{ |
|||
Items.Add(new BasketItem(productId, count)); |
|||
} |
|||
else |
|||
{ |
|||
item.Count += count; |
|||
} |
|||
} |
|||
|
|||
public void RemoveProduct(Guid productId, int? count = null) |
|||
{ |
|||
if (count is < 1) |
|||
{ |
|||
throw new ArgumentOutOfRangeException(nameof(count), "Product count should be null, 1 or more!"); |
|||
} |
|||
|
|||
var item = Items.FirstOrDefault(x => x.ProductId == productId); |
|||
if (item == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (count == null || item.Count <= count) |
|||
{ |
|||
Items.Remove(item); |
|||
return; |
|||
} |
|||
|
|||
item.Count -= count.Value; |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using System; |
|||
|
|||
namespace EShopOnAbp.BasketService; |
|||
|
|||
public class BasketItem |
|||
{ |
|||
public Guid ProductId { get; private set; } |
|||
public int Count { get; internal set; } |
|||
|
|||
private BasketItem() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public BasketItem(Guid productId, int count = 1) |
|||
{ |
|||
ProductId = productId; |
|||
Count = count; |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Caching; |
|||
|
|||
namespace EShopOnAbp.BasketService; |
|||
|
|||
public class BasketRepository : IBasketRepository |
|||
{ |
|||
private readonly IDistributedCache<Basket, Guid> _cache; |
|||
|
|||
public BasketRepository(IDistributedCache<Basket, Guid> cache) |
|||
{ |
|||
_cache = cache; |
|||
} |
|||
|
|||
public async Task<Basket> GetAsync(Guid id) |
|||
{ |
|||
return await _cache.GetOrAddAsync(id, () => Task.FromResult(new Basket(id))); |
|||
} |
|||
|
|||
public async Task UpdateAsync(Basket basket) |
|||
{ |
|||
await _cache.SetAsync(basket.Id, basket); |
|||
} |
|||
} |
|||
@ -1,16 +1,35 @@ |
|||
using Volo.Abp.Domain; |
|||
using System; |
|||
using Microsoft.Extensions.Caching.Distributed; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Abp.Domain; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace EShopOnAbp.BasketService |
|||
{ |
|||
[DependsOn( |
|||
typeof(BasketServiceDomainSharedModule), |
|||
typeof(AbpDddDomainModule) |
|||
typeof(AbpDddDomainModule), |
|||
typeof(AbpCachingModule) |
|||
)] |
|||
public class BasketServiceDomainModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpDistributedCacheOptions>(options => |
|||
{ |
|||
options.CacheConfigurators.Add(cacheName => |
|||
{ |
|||
if (cacheName == CacheNameAttribute.GetCacheName(typeof(Basket))) |
|||
{ |
|||
return new DistributedCacheEntryOptions |
|||
{ |
|||
SlidingExpiration = TimeSpan.FromDays(7) |
|||
}; |
|||
} |
|||
|
|||
return null; |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,12 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace EShopOnAbp.BasketService; |
|||
|
|||
public interface IBasketRepository : IRepository |
|||
{ |
|||
Task<Basket> GetAsync(Guid id); |
|||
|
|||
Task UpdateAsync(Basket basket); |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
// This file is automatically generated by ABP framework to use MVC Controllers from CSharp
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Http.Client; |
|||
using Volo.Abp.Http.Modeling; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Http.Client.ClientProxying; |
|||
using EShopOnAbp.BasketService; |
|||
|
|||
// ReSharper disable once CheckNamespace
|
|||
namespace EShopOnAbp.BasketService.ClientProxies |
|||
{ |
|||
[Dependency(ReplaceServices = true)] |
|||
[ExposeServices(typeof(IBasketAppService), typeof(BasketClientProxy))] |
|||
public partial class BasketClientProxy : ClientProxyBase<IBasketAppService>, IBasketAppService |
|||
{ |
|||
public virtual async Task<BasketDto> GetAsync() |
|||
{ |
|||
return await RequestAsync<BasketDto>(nameof(GetAsync)); |
|||
} |
|||
|
|||
public virtual async Task<BasketDto> AddProductAsync(AddProductDto input) |
|||
{ |
|||
return await RequestAsync<BasketDto>(nameof(AddProductAsync), new ClientProxyRequestTypeValue |
|||
{ |
|||
{ typeof(AddProductDto), input } |
|||
}); |
|||
} |
|||
|
|||
public virtual async Task<BasketDto> RemoveProductAsync(RemoveProductDto input) |
|||
{ |
|||
return await RequestAsync<BasketDto>(nameof(RemoveProductAsync), new ClientProxyRequestTypeValue |
|||
{ |
|||
{ typeof(RemoveProductDto), input } |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
// This file is part of BasketClientProxy, you can customize it here
|
|||
// ReSharper disable once CheckNamespace
|
|||
namespace EShopOnAbp.BasketService.ClientProxies |
|||
{ |
|||
public partial class BasketClientProxy |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,133 @@ |
|||
{ |
|||
"modules": { |
|||
"basket": { |
|||
"rootPath": "basket", |
|||
"remoteServiceName": "Basket", |
|||
"controllers": { |
|||
"EShopOnAbp.BasketService.BasketAppService": { |
|||
"controllerName": "Basket", |
|||
"controllerGroupName": "Basket", |
|||
"type": "EShopOnAbp.BasketService.BasketAppService", |
|||
"interfaces": [ |
|||
{ |
|||
"type": "Volo.Abp.Validation.IValidationEnabled" |
|||
}, |
|||
{ |
|||
"type": "Volo.Abp.Auditing.IAuditingEnabled" |
|||
}, |
|||
{ |
|||
"type": "Volo.Abp.GlobalFeatures.IGlobalFeatureCheckingEnabled" |
|||
}, |
|||
{ |
|||
"type": "EShopOnAbp.BasketService.IBasketAppService" |
|||
} |
|||
], |
|||
"actions": { |
|||
"GetAsync": { |
|||
"uniqueName": "GetAsync", |
|||
"name": "GetAsync", |
|||
"httpMethod": "GET", |
|||
"url": "api/basket/basket", |
|||
"supportedVersions": [], |
|||
"parametersOnMethod": [], |
|||
"parameters": [], |
|||
"returnValue": { |
|||
"type": "EShopOnAbp.BasketService.BasketDto", |
|||
"typeSimple": "EShopOnAbp.BasketService.BasketDto" |
|||
}, |
|||
"allowAnonymous": false, |
|||
"implementFrom": "EShopOnAbp.BasketService.IBasketAppService" |
|||
}, |
|||
"AddProductAsyncByInput": { |
|||
"uniqueName": "AddProductAsyncByInput", |
|||
"name": "AddProductAsync", |
|||
"httpMethod": "POST", |
|||
"url": "api/basket/basket/product", |
|||
"supportedVersions": [], |
|||
"parametersOnMethod": [ |
|||
{ |
|||
"name": "input", |
|||
"typeAsString": "EShopOnAbp.BasketService.AddProductDto, EShopOnAbp.BasketService.Application.Contracts", |
|||
"type": "EShopOnAbp.BasketService.AddProductDto", |
|||
"typeSimple": "EShopOnAbp.BasketService.AddProductDto", |
|||
"isOptional": false, |
|||
"defaultValue": null |
|||
} |
|||
], |
|||
"parameters": [ |
|||
{ |
|||
"nameOnMethod": "input", |
|||
"name": "input", |
|||
"jsonName": null, |
|||
"type": "EShopOnAbp.BasketService.AddProductDto", |
|||
"typeSimple": "EShopOnAbp.BasketService.AddProductDto", |
|||
"isOptional": false, |
|||
"defaultValue": null, |
|||
"constraintTypes": null, |
|||
"bindingSourceId": "Body", |
|||
"descriptorName": "" |
|||
} |
|||
], |
|||
"returnValue": { |
|||
"type": "EShopOnAbp.BasketService.BasketDto", |
|||
"typeSimple": "EShopOnAbp.BasketService.BasketDto" |
|||
}, |
|||
"allowAnonymous": false, |
|||
"implementFrom": "EShopOnAbp.BasketService.IBasketAppService" |
|||
}, |
|||
"RemoveProductAsyncByInput": { |
|||
"uniqueName": "RemoveProductAsyncByInput", |
|||
"name": "RemoveProductAsync", |
|||
"httpMethod": "DELETE", |
|||
"url": "api/basket/basket/product", |
|||
"supportedVersions": [], |
|||
"parametersOnMethod": [ |
|||
{ |
|||
"name": "input", |
|||
"typeAsString": "EShopOnAbp.BasketService.RemoveProductDto, EShopOnAbp.BasketService.Application.Contracts", |
|||
"type": "EShopOnAbp.BasketService.RemoveProductDto", |
|||
"typeSimple": "EShopOnAbp.BasketService.RemoveProductDto", |
|||
"isOptional": false, |
|||
"defaultValue": null |
|||
} |
|||
], |
|||
"parameters": [ |
|||
{ |
|||
"nameOnMethod": "input", |
|||
"name": "ProductId", |
|||
"jsonName": null, |
|||
"type": "System.Guid", |
|||
"typeSimple": "string", |
|||
"isOptional": false, |
|||
"defaultValue": null, |
|||
"constraintTypes": null, |
|||
"bindingSourceId": "ModelBinding", |
|||
"descriptorName": "input" |
|||
}, |
|||
{ |
|||
"nameOnMethod": "input", |
|||
"name": "Count", |
|||
"jsonName": null, |
|||
"type": "System.Int32?", |
|||
"typeSimple": "number?", |
|||
"isOptional": false, |
|||
"defaultValue": null, |
|||
"constraintTypes": null, |
|||
"bindingSourceId": "ModelBinding", |
|||
"descriptorName": "input" |
|||
} |
|||
], |
|||
"returnValue": { |
|||
"type": "EShopOnAbp.BasketService.BasketDto", |
|||
"typeSimple": "EShopOnAbp.BasketService.BasketDto" |
|||
}, |
|||
"allowAnonymous": false, |
|||
"implementFrom": "EShopOnAbp.BasketService.IBasketAppService" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"types": {} |
|||
} |
|||
Loading…
Reference in new issue