50 changed files with 1054 additions and 0 deletions
@ -0,0 +1,14 @@ |
|||
using AutoMapper; |
|||
using EShopOnAbp.CatalogService.Grpc; |
|||
using EShopOnAbp.CatalogService.Products; |
|||
|
|||
namespace EShopOnAbp.BasketService; |
|||
|
|||
public class BasketServiceApplicationAutoMapperProfile : Profile |
|||
{ |
|||
public BasketServiceApplicationAutoMapperProfile() |
|||
{ |
|||
CreateMap<ProductEto, ProductDto>(); |
|||
CreateMap<ProductResponse, ProductDto>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,184 @@ |
|||
using EShopOnAbp.BasketService.Entities; |
|||
using EShopOnAbp.BasketService.Localization; |
|||
using EShopOnAbp.CatalogService.Grpc; |
|||
using EShopOnAbp.Shared.Hosting.AspNetCore; |
|||
using EShopOnAbp.Shared.Hosting.Microservices; |
|||
using Microsoft.AspNetCore.Cors; |
|||
using Microsoft.Extensions.Caching.Distributed; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp; |
|||
using Volo.Abp.AspNetCore.Mvc.AntiForgery; |
|||
using Volo.Abp.AutoMapper; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Abp.Http.Client; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.Localization.ExceptionHandling; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.Validation.Localization; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace EShopOnAbp.BasketService |
|||
{ |
|||
[DependsOn( |
|||
)] |
|||
public class BasketServiceModule : AbpModule |
|||
{ |
|||
public const string RemoteServiceName = "Basket"; |
|||
|
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
var hostingEnvironment = context.Services.GetHostingEnvironment(); |
|||
var configuration = context.Services.GetConfiguration(); |
|||
|
|||
ConfigureAutoMapper(); |
|||
ConfigureGrpc(context); |
|||
ConfigureDistributedCache(); |
|||
ConfigureVirtualFileSystem(); |
|||
ConfigureLocalization(); |
|||
ConfigureHttpApiProxy(context); |
|||
ConfigureAuthentication(context, configuration); |
|||
} |
|||
|
|||
public override void OnApplicationInitialization(ApplicationInitializationContext context) |
|||
{ |
|||
var app = context.GetApplicationBuilder(); |
|||
var env = context.GetEnvironment(); |
|||
|
|||
if (env.IsDevelopment()) |
|||
{ |
|||
app.UseDeveloperExceptionPage(); |
|||
} |
|||
|
|||
app.UseCorrelationId(); |
|||
app.UseCors(); |
|||
app.UseAbpRequestLocalization(); |
|||
app.UseStaticFiles(); |
|||
app.UseRouting(); |
|||
app.UseAuthentication(); |
|||
app.UseAbpClaimsMap(); |
|||
app.UseAuthorization(); |
|||
app.UseSwagger(); |
|||
app.UseSwaggerUI(options => |
|||
{ |
|||
var configuration = context.ServiceProvider.GetRequiredService<IConfiguration>(); |
|||
options.SwaggerEndpoint("/swagger/v1/swagger.json", "Basket Service API"); |
|||
options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]); |
|||
options.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]); |
|||
}); |
|||
app.UseAbpSerilogEnrichers(); |
|||
app.UseAuditing(); |
|||
app.UseUnitOfWork(); |
|||
app.UseConfiguredEndpoints(); |
|||
} |
|||
|
|||
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) |
|||
{ |
|||
JwtBearerConfigurationHelper.Configure(context, "BasketService"); |
|||
|
|||
SwaggerWithAuthConfigurationHelper.Configure( |
|||
context: context, |
|||
authority: configuration["AuthServer:Authority"], |
|||
scopes: new |
|||
Dictionary<string, string> /* Requested scopes for authorization code request and descriptions for swagger UI only */ |
|||
{ |
|||
{"BasketService", "Basket Service API"} |
|||
}, |
|||
apiTitle: "Basket Service API" |
|||
); |
|||
|
|||
context.Services.AddCors(options => |
|||
{ |
|||
options.AddDefaultPolicy(builder => |
|||
{ |
|||
builder |
|||
.WithOrigins( |
|||
configuration["App:CorsOrigins"] |
|||
.Split(",", StringSplitOptions.RemoveEmptyEntries) |
|||
.Select(o => o.Trim().RemovePostFix("/")) |
|||
.ToArray() |
|||
) |
|||
.WithAbpExposedHeaders() |
|||
.SetIsOriginAllowedToAllowWildcardSubdomains() |
|||
.AllowAnyHeader() |
|||
.AllowAnyMethod() |
|||
.AllowCredentials(); |
|||
}); |
|||
}); |
|||
|
|||
Configure<AbpAntiForgeryOptions>(options => { options.AutoValidate = false; }); |
|||
} |
|||
|
|||
private void ConfigureHttpApiProxy(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.AddStaticHttpClientProxies( |
|||
typeof(BasketServiceApplicationContractsModule).Assembly, |
|||
RemoteServiceName |
|||
); |
|||
} |
|||
|
|||
private void ConfigureGrpc(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.AddGrpcClient<ProductPublic.ProductPublicClient>((services, options) => |
|||
{ |
|||
var remoteServiceOptions = services.GetRequiredService<IOptionsMonitor<AbpRemoteServiceOptions>>().CurrentValue; |
|||
var catalogServiceConfiguration = remoteServiceOptions.RemoteServices.GetConfigurationOrDefault("Catalog"); |
|||
var catalogGrpcUrl = catalogServiceConfiguration.GetOrDefault("GrpcUrl"); |
|||
|
|||
options.Address = new Uri(catalogGrpcUrl); |
|||
}); |
|||
} |
|||
|
|||
private void ConfigureAutoMapper() |
|||
{ |
|||
Configure<AbpAutoMapperOptions>(options => |
|||
{ |
|||
options.AddMaps<BasketServiceApplicationModule>(); |
|||
}); |
|||
} |
|||
|
|||
private void ConfigureDistributedCache() |
|||
{ |
|||
Configure<AbpDistributedCacheOptions>(options => |
|||
{ |
|||
options.CacheConfigurators.Add(cacheName => |
|||
{ |
|||
if (cacheName == CacheNameAttribute.GetCacheName(typeof(Basket))) |
|||
{ |
|||
return new DistributedCacheEntryOptions |
|||
{ |
|||
SlidingExpiration = TimeSpan.FromDays(7) |
|||
}; |
|||
} |
|||
|
|||
return null; |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
private void ConfigureVirtualFileSystem() |
|||
{ |
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<BasketServiceModule>(); |
|||
}); |
|||
} |
|||
|
|||
private void ConfigureLocalization() |
|||
{ |
|||
Configure<AbpLocalizationOptions>(options => |
|||
{ |
|||
options.Resources |
|||
.Add<BasketServiceResource>("en") |
|||
.AddBaseTypes(typeof(AbpValidationResource)) |
|||
.AddVirtualJson("/Localization/BasketService"); |
|||
|
|||
options.DefaultResourceType = typeof(BasketServiceResource); |
|||
}); |
|||
|
|||
Configure<AbpExceptionLocalizationOptions>(options => |
|||
{ |
|||
options.MapCodeNamespace("BasketService", typeof(BasketServiceResource)); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
|
|||
namespace EShopOnAbp.BasketService.Controllers; |
|||
|
|||
public class HomeController : AbpController |
|||
{ |
|||
public ActionResult Index() |
|||
{ |
|||
return Redirect("~/swagger"); |
|||
} |
|||
} |
|||
@ -0,0 +1,67 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk.Web"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0</TargetFramework> |
|||
<Nullable>enable</Nullable> |
|||
<ImplicitUsings>enable</ImplicitUsings> |
|||
</PropertyGroup> |
|||
|
|||
<!-- DOMAIN SHARED --> |
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.Validation" Version="5.1.2" /> |
|||
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="6.0.0" /> |
|||
<ProjectReference Include="..\..\..\ordering\src\EShopOnAbp.OrderingService.Domain.Shared\EShopOnAbp.OrderingService.Domain.Shared.csproj" /> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<EmbeddedResource Include="Localization\BasketService\*.json" /> |
|||
<Content Remove="Localization\BasketService\*.json" /> |
|||
</ItemGroup> |
|||
|
|||
<!-- DOMAIN --> |
|||
<ItemGroup> |
|||
<ProjectReference Include="..\EShopOnAbp.BasketService.Domain.Shared\EShopOnAbp.BasketService.Domain.Shared.csproj" /> |
|||
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="5.1.2" /> |
|||
<PackageReference Include="Volo.Abp.Caching" Version="5.1.2" /> |
|||
</ItemGroup> |
|||
|
|||
<!-- APPLICATION CONTRACTS --> |
|||
<ItemGroup> |
|||
<ProjectReference Include="..\EShopOnAbp.BasketService.Domain.Shared\EShopOnAbp.BasketService.Domain.Shared.csproj" /> |
|||
<PackageReference Include="Volo.Abp.Ddd.Application.Contracts" Version="5.1.2" /> |
|||
<PackageReference Include="Volo.Abp.Authorization" Version="5.1.2" /> |
|||
</ItemGroup> |
|||
|
|||
<!-- APPLICATION --> |
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.AutoMapper" Version="5.1.2" /> |
|||
<PackageReference Include="Volo.Abp.Ddd.Application" Version="5.1.2" /> |
|||
<PackageReference Include="Google.Protobuf" Version="3.19.3" /> |
|||
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.42.0" /> |
|||
<PackageReference Include="Grpc.Tools" Version="2.42.0" PrivateAssets="All" /> |
|||
<Protobuf Include="..\..\..\catalog\src\EShopOnAbp.CatalogService.Application.Contracts\Protos\product.proto" GrpcServices="Client" /> |
|||
<ProjectReference Include="..\EShopOnAbp.BasketService.Domain\EShopOnAbp.BasketService.Domain.csproj" /> |
|||
<ProjectReference Include="..\EShopOnAbp.BasketService.Application.Contracts\EShopOnAbp.BasketService.Application.Contracts.csproj" /> |
|||
<ProjectReference Include="..\..\..\catalog\src\EShopOnAbp.CatalogService.HttpApi.Client\EShopOnAbp.CatalogService.HttpApi.Client.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<!-- HTTP API --> |
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.AspNetCore.Mvc" Version="5.1.2" /> |
|||
<ProjectReference Include="..\EShopOnAbp.BasketService.Application.Contracts\EShopOnAbp.BasketService.Application.Contracts.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<!-- HTTP API HOST --> |
|||
<ItemGroup> |
|||
<ProjectReference Include="..\EShopOnAbp.BasketService.Application\EShopOnAbp.BasketService.Application.csproj" /> |
|||
<ProjectReference Include="..\EShopOnAbp.BasketService.HttpApi\EShopOnAbp.BasketService.HttpApi.csproj" /> |
|||
<ProjectReference Include="..\..\..\..\shared\EShopOnAbp.Shared.Hosting.Microservices\EShopOnAbp.Shared.Hosting.Microservices.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<Compile Remove="Logs\**" /> |
|||
<Content Remove="Logs\**" /> |
|||
<EmbeddedResource Remove="Logs\**" /> |
|||
<None Remove="Logs\**" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,77 @@ |
|||
using Volo.Abp.Domain.Entities; |
|||
|
|||
namespace EShopOnAbp.BasketService.Entities; |
|||
|
|||
public class Basket : AggregateRoot<Guid> |
|||
{ |
|||
public List<BasketItem> Items { get; 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; |
|||
} |
|||
|
|||
public int GetProductCount(Guid productId) |
|||
{ |
|||
var item = Items.FirstOrDefault(x => x.ProductId == productId); |
|||
return item?.Count ?? 0; |
|||
} |
|||
|
|||
public void Clear() |
|||
{ |
|||
Items.Clear(); |
|||
} |
|||
|
|||
public void Merge(Basket basket) |
|||
{ |
|||
foreach (var item in basket.Items) |
|||
{ |
|||
AddProduct(item.ProductId, item.Count); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
namespace EShopOnAbp.BasketService.Entities; |
|||
|
|||
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,7 @@ |
|||
namespace EShopOnAbp.BasketService.Entities; |
|||
|
|||
public class BasketProductUpdatedEto |
|||
{ |
|||
public Guid ProductId { get; set; } |
|||
public string ProductName { get; set; } |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
using Volo.Abp.Caching; |
|||
|
|||
namespace EShopOnAbp.BasketService.Entities; |
|||
|
|||
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); |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace EShopOnAbp.BasketService.Entities; |
|||
|
|||
public interface IBasketRepository : IRepository |
|||
{ |
|||
Task<Basket> GetAsync(Guid id); |
|||
|
|||
Task UpdateAsync(Basket basket); |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
using EShopOnAbp.OrderingService.Orders; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
using Volo.Abp.Uow; |
|||
|
|||
namespace EShopOnAbp.BasketService.Entities.Orders |
|||
{ |
|||
public class OrderPlacedEventHandler : IDistributedEventHandler<OrderPlacedEto>, ITransientDependency |
|||
{ |
|||
private readonly IBasketRepository _basketRepository; |
|||
|
|||
public OrderPlacedEventHandler(IBasketRepository basketRepository) |
|||
{ |
|||
_basketRepository = basketRepository; |
|||
} |
|||
|
|||
[UnitOfWork] |
|||
public async Task HandleEventAsync(OrderPlacedEto eventData) |
|||
{ |
|||
var basket = await _basketRepository.GetAsync(eventData.Buyer.BuyerId.GetValueOrDefault()); |
|||
basket.Clear(); |
|||
await _basketRepository.UpdateAsync(basket); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
{ |
|||
"culture": "ar", |
|||
"texts": { |
|||
"Menu:Home": "الرئيسية", |
|||
"Welcome": "مرحبا", |
|||
"LongWelcomeMessage": "مرحبا بكم في التطبيق. هذا مشروع بدء تشغيل يعتمد على إطار عمل ABP. لمزيد من المعلومات ، يرجى زيارة abp.io." |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
{ |
|||
"culture": "cs", |
|||
"texts": { |
|||
"Menu:Home": "Úvod", |
|||
"Welcome": "Vítejte", |
|||
"LongWelcomeMessage": "Vítejte v aplikaci. Toto je startovací projekt založený na ABP frameworku. Pro více informací, navštivte abp.io." |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
{ |
|||
"culture": "de-DE", |
|||
"texts": { |
|||
"Menu:Home": "Home", |
|||
"Welcome": "Willkommen", |
|||
"LongWelcomeMessage": "Willkommen bei der Anwendung. Dies ist ein Startup-Projekt, das auf dem ABP-Framework basiert. Weitere Informationen finden Sie unter abp.io." |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
{ |
|||
"culture": "en-GB", |
|||
"texts": { |
|||
"Menu:Home": "Home", |
|||
"Welcome": "Welcome", |
|||
"LongWelcomeMessage": "Welcome to the application. This is a startup project based on the ABP framework. For more information, visit abp.io." |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
{ |
|||
"culture": "en", |
|||
"texts": { |
|||
"Menu:Home": "Home", |
|||
"Welcome": "Welcome", |
|||
"LongWelcomeMessage": "Welcome to the application. This is a startup project based on the ABP framework. For more information, visit abp.io.", |
|||
"Toolbar:Cart": "My Cart", |
|||
"Toolbar:BasketNotLoaded": "Couldn't Load!", |
|||
"Toolbar:CartEmpty": "Cart is Empty!", |
|||
"ProductNotFound": "Could not find the product!" |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
{ |
|||
"culture": "es", |
|||
"texts": { |
|||
"Menu:Home": "Inicio", |
|||
"Welcome": "Bienvenido", |
|||
"LongWelcomeMessage": "Bienvenido a la aplicación, este es un proyecto base basado en el framework ABP. Para más información, visita abp.io." |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
{ |
|||
"culture": "fi", |
|||
"texts": { |
|||
"Menu:Home": "Koti", |
|||
"Welcome": "Tervetuloa", |
|||
"LongWelcomeMessage": "Tervetuloa sovellukseen. Tämä on ABP-kehykseen perustuva käynnistysprojekti. Lisätietoja on osoitteessa abp.io." |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
{ |
|||
"culture": "fr", |
|||
"texts": { |
|||
"Menu:Home": "Accueil", |
|||
"Welcome": "Bienvenue", |
|||
"LongWelcomeMessage": "Bienvenue dans l'application. Il s'agit d'un projet de démarrage basé sur le framework ABP. Pour plus d'informations, visitez abp.io." |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
{ |
|||
"culture": "hi", |
|||
"texts": { |
|||
"Menu:Home": "घर", |
|||
"Welcome": "स्वागत हे", |
|||
"LongWelcomeMessage": "आवेदन करने के लिए आपका स्वागत है। यह एबीपी ढांचे पर आधारित एक स्टार्टअप परियोजना है। अधिक जानकारी के लिए, abp.io पर जाएं।" |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
{ |
|||
"culture": "hu", |
|||
"texts": { |
|||
"Menu:Home": "Kezdőlap", |
|||
"Welcome": "Üdvözlöm", |
|||
"LongWelcomeMessage": "Üdvözöljük az alkalmazásban. Ez egy ABP keretrendszeren alapuló startup projekt. További információkért látogasson el az abp.io oldalra." |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
{ |
|||
"culture": "it", |
|||
"texts": { |
|||
"Menu:Home": "Home", |
|||
"Welcome": "Benvenuto", |
|||
"LongWelcomeMessage": "Benvenuto nell'applicazione. Questo è un progetto di avvio basato sul framework ABP. Per ulteriori informazioni, visita abp.io." |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
{ |
|||
"culture": "nl", |
|||
"texts": { |
|||
"Menu:Home": "Home", |
|||
"Welcome": "Welkom", |
|||
"LongWelcomeMessage": "Welkom bij de applicatie. Dit is een startup-project gebaseerd op het ABP-framework. Bezoek abp.io voor meer informatie." |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
{ |
|||
"culture": "pl-PL", |
|||
"texts": { |
|||
"Menu:Home": "Home", |
|||
"Welcome": "Witaj", |
|||
"LongWelcomeMessage": "Witaj w aplikacji. To jest inicjalny projekt bazujący na ABP framework. Po więcej informacji odwiedź stronę abp.io." |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
{ |
|||
"culture": "pt-BR", |
|||
"texts": { |
|||
"Menu:Home": "Principal", |
|||
"Welcome": "Seja bem-vindo!", |
|||
"LongWelcomeMessage": "Bem-vindo a esta aplicação. Este é um projeto inicial baseado no ABP framework. Para mais informações, visite abp.io." |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
{ |
|||
"culture": "ro-RO", |
|||
"texts": { |
|||
"Menu:Home": "Acasă", |
|||
"Welcome": "Bun venit", |
|||
"LongWelcomeMessage": "Bun venit la aplicaţie. Acesta este un proiect de pornire bazat pe framework-ul ABP. Pentru mai multe informaţii, vizitaţi, visit abp.io." |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
{ |
|||
"culture": "ru", |
|||
"texts": { |
|||
"Menu:Home": "Главная", |
|||
"Welcome": "Добро пожаловать", |
|||
"LongWelcomeMessage": "Добро пожаловать в приложение. Этот запущенный проект основан на фреймворке ABP. Для получения дополнительной информации посетите сайт abp.io." |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
{ |
|||
"culture": "sk", |
|||
"texts": { |
|||
"Menu:Home": "Domov", |
|||
"Welcome": "Vitajte", |
|||
"LongWelcomeMessage": "Vitajte v aplikácii. Toto je štartovací projekt založený na ABP frameworku. Viac informácií nájdete na stránke abp.io." |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
{ |
|||
"culture": "sl", |
|||
"texts": { |
|||
"Menu:Home": "Domov", |
|||
"Welcome": "Dobrodošli", |
|||
"LongWelcomeMessage": "Dobrodošli v aplikaciji. To je začetni projekt na osnovi okolja ABP. Za več informacij obiščite abp.io." |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
{ |
|||
"culture": "tr", |
|||
"texts": { |
|||
"Menu:Home": "Ana sayfa", |
|||
"Welcome": "Hoşgeldiniz", |
|||
"LongWelcomeMessage": "Uygulamaya hoşgeldiniz. Bu, ABP framework'ü üzerine bina edilmiş bir başlangıç projesidir. Daha fazla bilgi için abp.io adresini ziyaret edebilirsiniz.", |
|||
"Toolbar:Cart": "Sepetim", |
|||
"Toolbar:BasketNotLoaded": "Sepet Hatası!", |
|||
"Toolbar:CartEmpty": "Sepetiniz Boş", |
|||
"ProductNotFound": "Bu ürün bulunamadı!" |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
{ |
|||
"culture": "vi", |
|||
"texts": { |
|||
"Menu:Home": "Trang chủ", |
|||
"Welcome": "Chào mừng bạn", |
|||
"LongWelcomeMessage": "Chào mừng bạn đến ứng dụng. Đây là một dự án khởi nghiệp dựa trên khung ABP. Để biết thêm thông tin, hãy truy cập abp.io." |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
{ |
|||
"culture": "zh-Hans", |
|||
"texts": { |
|||
"Menu:Home": "首页", |
|||
"Welcome": "欢迎", |
|||
"LongWelcomeMessage": "欢迎来到该应用程序. 这是一个基于ABP框架的启动项目. 有关更多信息, 请访问 abp.io.", |
|||
"Toolbar:Cart": "我的购物车", |
|||
"Toolbar:BasketNotLoaded": "无法加载!" |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
{ |
|||
"culture": "zh-Hant", |
|||
"texts": { |
|||
"Menu:Home": "首頁", |
|||
"Welcome": "歡迎", |
|||
"LongWelcomeMessage": "歡迎來到此應用程式. 這是一個基於ABP框架的起始專案. 有關更多訊息, 請瀏覽 abp.io." |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace EShopOnAbp.BasketService.Localization |
|||
{ |
|||
[LocalizationResourceName("BasketService")] |
|||
public class BasketServiceResource |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
var builder = WebApplication.CreateBuilder(args); |
|||
var app = builder.Build(); |
|||
|
|||
app.MapGet("/", () => "Hello World!"); |
|||
|
|||
app.Run(); |
|||
@ -0,0 +1,28 @@ |
|||
{ |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:43756", |
|||
"sslPort": 44365 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"EShopOnAbp.BasketService": { |
|||
"commandName": "Project", |
|||
"dotnetRunMessages": true, |
|||
"launchBrowser": true, |
|||
"applicationUrl": "https://localhost:7198;http://localhost:5198", |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
}, |
|||
"IIS Express": { |
|||
"commandName": "IISExpress", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,124 @@ |
|||
using EShopOnAbp.BasketService.Entities; |
|||
using EShopOnAbp.BasketService.Localization; |
|||
using EShopOnAbp.BasketService.Services.Dtos; |
|||
using EShopOnAbp.CatalogService.Products; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Application.Services; |
|||
using Volo.Abp.Users; |
|||
|
|||
namespace EShopOnAbp.BasketService.Services; |
|||
|
|||
public class BasketAppService : ApplicationService, IBasketAppService |
|||
{ |
|||
private readonly IBasketRepository _basketRepository; |
|||
private readonly IBasketProductService _basketProductService; |
|||
|
|||
public BasketAppService( |
|||
IBasketRepository basketRepository, |
|||
IBasketProductService basketProductService) |
|||
{ |
|||
_basketRepository = basketRepository; |
|||
_basketProductService = basketProductService; |
|||
LocalizationResource = typeof(BasketServiceResource); |
|||
} |
|||
|
|||
public async Task<BasketDto> GetAsync(Guid? anonymousUserId) |
|||
{ |
|||
if (anonymousUserId != null && CurrentUser.IsAuthenticated) |
|||
{ |
|||
var userBasket = await _basketRepository.GetAsync(CurrentUser.GetId()); |
|||
var anonymousUserBasket = await _basketRepository.GetAsync(anonymousUserId.Value); |
|||
|
|||
userBasket.Merge(anonymousUserBasket); |
|||
await _basketRepository.UpdateAsync(userBasket); |
|||
|
|||
anonymousUserBasket.Clear(); |
|||
await _basketRepository.UpdateAsync(anonymousUserBasket); |
|||
|
|||
return await GetBasketDtoAsync(userBasket); |
|||
} |
|||
|
|||
if (anonymousUserId != null && !CurrentUser.IsAuthenticated) |
|||
{ |
|||
return await GetBasketDtoAsync(await _basketRepository.GetAsync(anonymousUserId.Value)); |
|||
} |
|||
|
|||
return await GetBasketDtoAsync(await _basketRepository.GetAsync(CurrentUser.GetId())); |
|||
} |
|||
|
|||
public async Task<BasketDto> AddProductAsync(AddProductDto input) |
|||
{ |
|||
Guid userId = CurrentUser.IsAuthenticated ? CurrentUser.GetId() : input.AnonymousId.GetValueOrDefault(); |
|||
|
|||
var basket = await _basketRepository.GetAsync(userId); |
|||
var product = await _basketProductService.GetAsync(input.ProductId); |
|||
|
|||
if (basket.GetProductCount(product.Id) >= product.StockCount) |
|||
{ |
|||
throw new UserFriendlyException("There is not enough product in stock, sorry :("); |
|||
} |
|||
|
|||
basket.AddProduct(product.Id); |
|||
|
|||
await _basketRepository.UpdateAsync(basket); |
|||
|
|||
return await GetBasketDtoAsync(basket); |
|||
} |
|||
|
|||
public async Task<BasketDto> RemoveProductAsync(RemoveProductDto input) |
|||
{ |
|||
Guid userId = CurrentUser.IsAuthenticated ? CurrentUser.GetId() : input.AnonymousId.GetValueOrDefault(); |
|||
|
|||
var basket = await _basketRepository.GetAsync(userId); |
|||
var product = await _basketProductService.GetAsync(input.ProductId); |
|||
|
|||
basket.RemoveProduct(product.Id, input.Count); |
|||
|
|||
await _basketRepository.UpdateAsync(basket); |
|||
|
|||
return await GetBasketDtoAsync(basket); |
|||
} |
|||
|
|||
private async Task<BasketDto> GetBasketDtoAsync(Basket basket) |
|||
{ |
|||
var products = new Dictionary<Guid, ProductDto>(); |
|||
var basketDto = new BasketDto(); |
|||
var basketChanged = false; |
|||
|
|||
foreach (var basketItem in basket.Items) |
|||
{ |
|||
var productDto = products.GetOrDefault(basketItem.ProductId); |
|||
if (productDto == null) |
|||
{ |
|||
productDto = await _basketProductService.GetAsync(basketItem.ProductId); |
|||
products[productDto.Id] = productDto; |
|||
} |
|||
|
|||
//Removing the products if not available in the stock
|
|||
if (basketItem.Count > productDto.StockCount) |
|||
{ |
|||
basket.RemoveProduct(basketItem.ProductId, basketItem.Count - productDto.StockCount); |
|||
basketChanged = true; |
|||
} |
|||
|
|||
basketDto.Items.Add(new BasketItemDto |
|||
{ |
|||
ProductId = basketItem.ProductId, |
|||
Count = basketItem.Count, |
|||
ProductCode = productDto.Code, |
|||
ImageName = productDto.ImageName, |
|||
ProductName = productDto.Name, |
|||
TotalPrice = productDto.Price * basketItem.Count |
|||
}); |
|||
} |
|||
|
|||
basketDto.TotalPrice = basketDto.Items.Sum(x => x.TotalPrice); |
|||
|
|||
if (basketChanged) |
|||
{ |
|||
await _basketRepository.UpdateAsync(basket); |
|||
} |
|||
|
|||
return basketDto; |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
using EShopOnAbp.BasketService.Entities; |
|||
using EShopOnAbp.CatalogService.Products; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Domain.Entities.Events.Distributed; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
using Volo.Abp.ObjectMapping; |
|||
|
|||
namespace EShopOnAbp.BasketService.Services; |
|||
|
|||
public class BasketProductEventHandler : IDistributedEventHandler<EntityUpdatedEto<ProductEto>>, ITransientDependency |
|||
{ |
|||
private readonly IDistributedCache<ProductDto, Guid> _cache; |
|||
private readonly IObjectMapper _objectMapper; |
|||
private readonly IDistributedEventBus _distributedEventBus; |
|||
|
|||
public BasketProductEventHandler( |
|||
IDistributedCache<ProductDto, Guid> cache, |
|||
IObjectMapper objectMapper, |
|||
IDistributedEventBus distributedEventBus) |
|||
{ |
|||
_cache = cache; |
|||
_objectMapper = objectMapper; |
|||
_distributedEventBus = distributedEventBus; |
|||
} |
|||
|
|||
public async Task HandleEventAsync(EntityUpdatedEto<ProductEto> eventData) |
|||
{ |
|||
var cachedProductDto = await _cache.GetAsync(eventData.Entity.Id); |
|||
if (cachedProductDto == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
_objectMapper.Map(eventData.Entity, cachedProductDto); |
|||
|
|||
await _cache.SetAsync(eventData.Entity.Id, cachedProductDto); |
|||
|
|||
await _distributedEventBus.PublishAsync( |
|||
new BasketProductUpdatedEto |
|||
{ |
|||
ProductId = eventData.Entity.Id, |
|||
ProductName = eventData.Entity.Name |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
using EShopOnAbp.CatalogService.Grpc; |
|||
using EShopOnAbp.CatalogService.Products; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.ObjectMapping; |
|||
|
|||
namespace EShopOnAbp.BasketService.Services; |
|||
|
|||
public class BasketProductService : IBasketProductService, ITransientDependency |
|||
{ |
|||
private readonly IDistributedCache<ProductDto, Guid> _cache; |
|||
private readonly ILogger<BasketProductService> _logger; |
|||
private readonly IObjectMapper _mapper; |
|||
private readonly ProductPublic.ProductPublicClient _productPublicGrpcClient; |
|||
|
|||
public BasketProductService( |
|||
IDistributedCache<ProductDto, Guid> cache, |
|||
ILogger<BasketProductService> logger, |
|||
IObjectMapper mapper, |
|||
ProductPublic.ProductPublicClient productPublicGrpcClient) |
|||
{ |
|||
_cache = cache; |
|||
_logger = logger; |
|||
_mapper = mapper; |
|||
_productPublicGrpcClient = productPublicGrpcClient; |
|||
} |
|||
|
|||
public async Task<ProductDto> GetAsync(Guid productId) |
|||
{ |
|||
return await _cache.GetOrAddAsync( |
|||
productId, |
|||
() => GetProductAsync(productId) |
|||
); |
|||
} |
|||
|
|||
private async Task<ProductDto> GetProductAsync(Guid productId) |
|||
{ |
|||
var request = new ProductRequest { Id = productId.ToString() }; |
|||
_logger.LogInformation("=== GRPC request {@request}", request); |
|||
var response = await _productPublicGrpcClient.GetByIdAsync(request); |
|||
_logger.LogInformation("=== GRPC response {@response}", response); |
|||
return _mapper.Map<ProductResponse, ProductDto>(response) ?? |
|||
throw new UserFriendlyException(BasketServiceDomainErrorCodes.ProductNotFound); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
|
|||
namespace EShopOnAbp.BasketService.Services.Dtos; |
|||
|
|||
public class AddProductDto : IHasAnonymousId |
|||
{ |
|||
public Guid ProductId { get; set; } |
|||
|
|||
[Range(1, int.MaxValue)] |
|||
public int Count { get; set; } = 1; |
|||
|
|||
public Guid? AnonymousId { get; set; } |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
namespace EShopOnAbp.BasketService.Services.Dtos; |
|||
|
|||
public class BasketDto |
|||
{ |
|||
public float TotalPrice { get; set; } |
|||
public List<BasketItemDto> Items { get; set; } |
|||
|
|||
public BasketDto() |
|||
{ |
|||
Items = new List<BasketItemDto>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
namespace EShopOnAbp.BasketService.Services.Dtos; |
|||
|
|||
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; } |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace EShopOnAbp.BasketService.Services.Dtos |
|||
{ |
|||
public interface IHasAnonymousId |
|||
{ |
|||
Guid? AnonymousId { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
namespace EShopOnAbp.BasketService.Services.Dtos; |
|||
|
|||
public class RemoveProductDto : IHasAnonymousId |
|||
{ |
|||
public Guid ProductId { get; set; } |
|||
|
|||
public int? Count { get; set; } |
|||
|
|||
public Guid? AnonymousId { get; set; } |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using EShopOnAbp.BasketService.Services.Dtos; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace EShopOnAbp.BasketService.Services; |
|||
|
|||
public interface IBasketAppService : IApplicationService |
|||
{ |
|||
Task<BasketDto> GetAsync(Guid? anonymousUserId); |
|||
Task<BasketDto> AddProductAsync(AddProductDto input); |
|||
Task<BasketDto> RemoveProductAsync(RemoveProductDto input); |
|||
} |
|||
@ -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,21 @@ |
|||
using EShopOnAbp.BasketService.Localization; |
|||
using Volo.Abp.Authorization.Permissions; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace EShopOnAbp.BasketService.Services.Permissions |
|||
{ |
|||
public class BasketServicePermissionDefinitionProvider : PermissionDefinitionProvider |
|||
{ |
|||
public override void Define(IPermissionDefinitionContext context) |
|||
{ |
|||
var myGroup = context.AddGroup(BasketServicePermissions.GroupName); |
|||
//Define your own permissions here. Example:
|
|||
//myGroup.AddPermission(BasketServicePermissions.MyPermission1, L("Permission:MyPermission1"));
|
|||
} |
|||
|
|||
private static LocalizableString L(string name) |
|||
{ |
|||
return LocalizableString.Create<BasketServiceResource>(name); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
namespace EShopOnAbp.BasketService.Services.Permissions |
|||
{ |
|||
public static class BasketServicePermissions |
|||
{ |
|||
public const string GroupName = "BasketService"; |
|||
|
|||
//Add your own permission names. Example:
|
|||
//public const string MyPermission1 = GroupName + ".MyPermission1";
|
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
{ |
|||
"Logging": { |
|||
"LogLevel": { |
|||
"Default": "Information", |
|||
"Microsoft.AspNetCore": "Warning" |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
{ |
|||
"App": { |
|||
"SelfUrl": "https://localhost:44355", |
|||
"CorsOrigins": "https://localhost:44372,https://localhost:44373,https://localhost:44335" |
|||
}, |
|||
"AuthServer": { |
|||
"Authority": "https://localhost:44330", |
|||
"RequireHttpsMetadata": "true", |
|||
"SwaggerClientId": "WebGateway_Swagger", |
|||
"SwaggerClientSecret": "1q2w3e*" |
|||
}, |
|||
"RemoteServices": { |
|||
"Catalog": { |
|||
"BaseUrl": "https://localhost:44354/", |
|||
"GrpcUrl": "http://localhost:81" |
|||
} |
|||
}, |
|||
"Logging": { |
|||
"LogLevel": { |
|||
"Default": "Information", |
|||
"Microsoft": "Warning", |
|||
"Microsoft.Hosting.Lifetime": "Information" |
|||
} |
|||
}, |
|||
"AllowedHosts": "*", |
|||
"ConnectionStrings": { |
|||
"AdministrationService": "User ID=postgres;Password=myPassw0rd;Host=localhost;Port=5432;Database=EShopOnAbp_Administration;Pooling=false;" |
|||
}, |
|||
"StringEncryption": { |
|||
"DefaultPassPhrase": "3PfxLgcuymUUbihz" |
|||
}, |
|||
"Redis": { |
|||
"Configuration": "localhost:6379" |
|||
}, |
|||
"RabbitMQ": { |
|||
"Connections": { |
|||
"Default": { |
|||
"HostName": "localhost" |
|||
} |
|||
}, |
|||
"EventBus": { |
|||
"ClientName": "EShopOnAbp_BasketService", |
|||
"ExchangeName": "EShopOnAbp" |
|||
} |
|||
}, |
|||
"ElasticSearch": { |
|||
"Url": "http://localhost:9200" |
|||
} |
|||
} |
|||
Loading…
Reference in new issue