From cda58e4b935bd28baf18216445817b432183e5ef Mon Sep 17 00:00:00 2001 From: daxnet Date: Sat, 14 Jul 2018 19:53:14 +0800 Subject: [PATCH] =?UTF-8?q?=E7=B3=BB=E5=88=97=E6=96=87=E7=AB=A0=E7=AC=AC5?= =?UTF-8?q?=E9=83=A8=E5=88=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- EdaSample.sln | 23 ++- .../DataAccess/IDataAccessObject.cs | 73 ++++++++ .../EdaSample.DataAccess.MongoDB.csproj | 15 ++ .../MongoDataAccessObject.cs | 168 ++++++++++++++++++ .../RabbitMQEventBus.cs | 13 +- .../EdaSample.Services.Common.csproj | 11 ++ .../Events/CustomerCreatedEvent.cs | 13 +- .../Controllers/CustomersController.cs | 25 +-- .../EdaSample.Services.Customer.csproj | 1 + .../AddNewCustomerEventHandler.cs | 41 +++++ .../CustomerCreatedEventHandler.cs | 12 +- .../Model/Customer.cs | 2 + .../Model/MssqlConfig.cs | 12 ++ src/EdaSample.Services.Customer/Startup.cs | 8 +- .../Controllers/ValuesController.cs | 44 +++++ .../EdaSample.Services.Notification.csproj | 28 +++ .../CustomerCreatedEventHandler.cs | 27 +++ .../Program.cs | 31 ++++ .../Startup.cs | 73 ++++++++ .../appsettings.Development.json | 15 ++ .../appsettings.json | 15 ++ 21 files changed, 619 insertions(+), 31 deletions(-) create mode 100644 src/EdaSample.Common/DataAccess/IDataAccessObject.cs create mode 100644 src/EdaSample.DataAccess.MongoDB/EdaSample.DataAccess.MongoDB.csproj create mode 100644 src/EdaSample.DataAccess.MongoDB/MongoDataAccessObject.cs create mode 100644 src/EdaSample.Services.Common/EdaSample.Services.Common.csproj rename src/{EdaSample.Services.Customer => EdaSample.Services.Common}/Events/CustomerCreatedEvent.cs (57%) create mode 100644 src/EdaSample.Services.Customer/EventHandlers/AddNewCustomerEventHandler.cs create mode 100644 src/EdaSample.Services.Customer/Model/MssqlConfig.cs create mode 100644 src/EdaSample.Services.Notification/Controllers/ValuesController.cs create mode 100644 src/EdaSample.Services.Notification/EdaSample.Services.Notification.csproj create mode 100644 src/EdaSample.Services.Notification/EventHandlers/CustomerCreatedEventHandler.cs create mode 100644 src/EdaSample.Services.Notification/Program.cs create mode 100644 src/EdaSample.Services.Notification/Startup.cs create mode 100644 src/EdaSample.Services.Notification/appsettings.Development.json create mode 100644 src/EdaSample.Services.Notification/appsettings.json diff --git a/EdaSample.sln b/EdaSample.sln index 36010e0..3e13863 100644 --- a/EdaSample.sln +++ b/EdaSample.sln @@ -21,7 +21,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdaSample.EventBus.RabbitMQ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "2- Tests", "2- Tests", "{0B52AB70-8ABB-4BA2-A2B4-BC0C6CAC110B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EdaSample.Tests", "src\EdaSample.Tests\EdaSample.Tests.csproj", "{890C9EE5-E759-445E-BA3F-0CA0FA9B05B7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdaSample.Tests", "src\EdaSample.Tests\EdaSample.Tests.csproj", "{890C9EE5-E759-445E-BA3F-0CA0FA9B05B7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdaSample.Services.Common", "src\EdaSample.Services.Common\EdaSample.Services.Common.csproj", "{D287D127-DB6E-46A3-B475-8AC1069F9ED5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdaSample.Services.Notification", "src\EdaSample.Services.Notification\EdaSample.Services.Notification.csproj", "{9125189E-FFF3-401D-9C76-23CF489439B9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EdaSample.DataAccess.MongoDB", "src\EdaSample.DataAccess.MongoDB\EdaSample.DataAccess.MongoDB.csproj", "{C3340AC8-C7AE-4B68-939F-9F76845F871A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -57,6 +63,18 @@ Global {890C9EE5-E759-445E-BA3F-0CA0FA9B05B7}.Debug|Any CPU.Build.0 = Debug|Any CPU {890C9EE5-E759-445E-BA3F-0CA0FA9B05B7}.Release|Any CPU.ActiveCfg = Release|Any CPU {890C9EE5-E759-445E-BA3F-0CA0FA9B05B7}.Release|Any CPU.Build.0 = Release|Any CPU + {D287D127-DB6E-46A3-B475-8AC1069F9ED5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D287D127-DB6E-46A3-B475-8AC1069F9ED5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D287D127-DB6E-46A3-B475-8AC1069F9ED5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D287D127-DB6E-46A3-B475-8AC1069F9ED5}.Release|Any CPU.Build.0 = Release|Any CPU + {9125189E-FFF3-401D-9C76-23CF489439B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9125189E-FFF3-401D-9C76-23CF489439B9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9125189E-FFF3-401D-9C76-23CF489439B9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9125189E-FFF3-401D-9C76-23CF489439B9}.Release|Any CPU.Build.0 = Release|Any CPU + {C3340AC8-C7AE-4B68-939F-9F76845F871A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C3340AC8-C7AE-4B68-939F-9F76845F871A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C3340AC8-C7AE-4B68-939F-9F76845F871A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C3340AC8-C7AE-4B68-939F-9F76845F871A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -69,6 +87,9 @@ Global {37C1B71F-8BB7-4800-BFCA-FE6DC07DEF17} = {865391C4-CCCF-4B27-A784-C02794202855} {845C94EC-0023-4DD4-BB9E-32E5A9EBC2F3} = {865391C4-CCCF-4B27-A784-C02794202855} {890C9EE5-E759-445E-BA3F-0CA0FA9B05B7} = {0B52AB70-8ABB-4BA2-A2B4-BC0C6CAC110B} + {D287D127-DB6E-46A3-B475-8AC1069F9ED5} = {93BE142E-830C-4FBF-9267-8FD7472F088F} + {9125189E-FFF3-401D-9C76-23CF489439B9} = {93BE142E-830C-4FBF-9267-8FD7472F088F} + {C3340AC8-C7AE-4B68-939F-9F76845F871A} = {865391C4-CCCF-4B27-A784-C02794202855} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {735DB5FF-3ED8-4D88-8999-2C02E6AAABB1} diff --git a/src/EdaSample.Common/DataAccess/IDataAccessObject.cs b/src/EdaSample.Common/DataAccess/IDataAccessObject.cs new file mode 100644 index 0000000..8ce466d --- /dev/null +++ b/src/EdaSample.Common/DataAccess/IDataAccessObject.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace EdaSample.Common.DataAccess +{ + /// + /// Represents that the implemented classes are data access objects that perform + /// CRUD operations on the given entity type. + /// + /// + public interface IDataAccessObject : IDisposable + { + /// + /// Gets the entity by specified identifier asynchronously. + /// + /// The type of the entity. + /// The identifier of the entity. + /// The task which performs the data retrieval operation. + Task GetByIdAsync(Guid id) + where TEntity : IEntity; + + /// + /// Gets all of the entities asynchronously. + /// + /// The type of the entity. + /// The task which performs the data retrieval operation, and after + /// the operation has completed, would return a list of retrieved entities. + /// + Task> GetAllAsync() + where TEntity : IEntity; + + /// + /// Adds the given entity asynchronously. + /// + /// The type of the entity. + /// The entity to be added. + /// The task which performs the adding operation. + Task AddAsync(TEntity entity) + where TEntity : IEntity; + + /// + /// Updates the entity by the specified identifier asynchronously. + /// + /// The type of the entity. + /// The identifier of the entity to be updated. + /// The entity which contains the updated value. + /// The task which performs the updating operation. + Task UpdateByIdAsync(Guid id, TEntity entity) + where TEntity : IEntity; + + /// + /// Finds the entities which match the specified criteria that is defined by the given specification asynchronously. + /// + /// The type of the entity. + /// The expression which defines the matching criteria. + /// The task which performs the data retrieval operation, and after the operation + /// has completed, would return a list of entities that match the specified criteria. + Task> FindBySpecificationAsync(Expression> expr) + where TEntity : IEntity; + + /// + /// Deletes the entity by specified identifier asynchronously. + /// + /// The type of the entity. + /// The identifier which represents the entity that is going to be deleted. + /// The task which performs the deletion operation. + Task DeleteByIdAsync(Guid id) + where TEntity : IEntity; + } +} diff --git a/src/EdaSample.DataAccess.MongoDB/EdaSample.DataAccess.MongoDB.csproj b/src/EdaSample.DataAccess.MongoDB/EdaSample.DataAccess.MongoDB.csproj new file mode 100644 index 0000000..22ec41b --- /dev/null +++ b/src/EdaSample.DataAccess.MongoDB/EdaSample.DataAccess.MongoDB.csproj @@ -0,0 +1,15 @@ + + + + netcoreapp2.0 + + + + + + + + + + + diff --git a/src/EdaSample.DataAccess.MongoDB/MongoDataAccessObject.cs b/src/EdaSample.DataAccess.MongoDB/MongoDataAccessObject.cs new file mode 100644 index 0000000..a1d8ee0 --- /dev/null +++ b/src/EdaSample.DataAccess.MongoDB/MongoDataAccessObject.cs @@ -0,0 +1,168 @@ +using EdaSample.Common; +using EdaSample.Common.DataAccess; +using MongoDB.Driver; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; + +namespace EdaSample.DataAccess.MongoDB +{ + /// + /// Represents the data access object which manipulates the MongoDB database. + /// + /// + public sealed class MongoDataAccessObject : IDataAccessObject + { + #region Private Fields + + private readonly IMongoClient client; + private readonly IMongoDatabase database; + + private bool disposedValue = false; + + #endregion Private Fields + + #region Public Constructors + + /// + /// Initializes a new instance of the class. + /// + /// Name of the database. + /// The server on which the MongoDB database has deployed. + /// The port number to which the MongoDB database is listening. + public MongoDataAccessObject(string databaseName, string server = "localhost", int port = 27017) + { + this.client = new MongoClient($"mongodb://{server}:{port}/{databaseName}"); + this.database = this.client.GetDatabase(databaseName); + } + + #endregion Public Constructors + + #region Public Methods + + /// + /// Adds the given entity asynchronously. + /// + /// The type of the entity. + /// The entity to be added. + /// + /// The task which performs the adding operation. + /// + public async Task AddAsync(TEntity entity) where TEntity : IEntity + { + var collection = GetCollection(); + var options = new InsertOneOptions { BypassDocumentValidation = true }; + await collection.InsertOneAsync(entity, options); + } + + /// + /// Deletes the entity by specified identifier asynchronously. + /// + /// The type of the entity. + /// The identifier which represents the entity that is going to be deleted. + /// + /// The task which performs the deletion operation. + /// + public async Task DeleteByIdAsync(Guid id) where TEntity : IEntity + { + var filterDefinition = Builders.Filter.Eq(x => x.Id, id); + await GetCollection().DeleteOneAsync(filterDefinition); + } + + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + // TODO: uncomment the following line if the finalizer is overridden above. + // GC.SuppressFinalize(this); + } + + /// + /// Finds the entities which match the specified criteria that is defined by the given specification asynchronously. + /// + /// The type of the entity. + /// The expression which defines the matching criteria. + /// + /// The task which performs the data retrieval operation, and after the operation + /// has completed, would return a list of entities that match the specified criteria. + /// + public async Task> FindBySpecificationAsync(Expression> expr) where TEntity : IEntity + => await (await GetCollection().FindAsync(expr)).ToListAsync(); + + /// + /// Gets all of the entities asynchronously. + /// + /// The type of the entity. + /// + /// The task which performs the data retrieval operation, and after + /// the operation has completed, would return a list of retrieved entities. + /// + public async Task> GetAllAsync() where TEntity : IEntity => + await FindBySpecificationAsync(_ => true); + + /// + /// Gets the entity by specified identifier asynchronously. + /// + /// The type of the entity. + /// The identifier of the entity. + /// + /// The task which performs the data retrieval operation. + /// + public async Task GetByIdAsync(Guid id) where TEntity : IEntity => + (await FindBySpecificationAsync(x => x.Id.Equals(id))).FirstOrDefault(); + + /// + /// Updates the entity by the specified identifier asynchronously. + /// + /// The type of the entity. + /// The identifier of the entity to be updated. + /// The entity which contains the updated value. + /// + /// The task which performs the updating operation. + /// + public async Task UpdateByIdAsync(Guid id, TEntity entity) where TEntity : IEntity + { + var filterDefinition = Builders.Filter.Eq(x => x.Id, id); + await GetCollection().ReplaceOneAsync(filterDefinition, entity); + } + + #endregion Public Methods + + #region Private Methods + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + private void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // TODO: dispose managed state (managed objects). + } + + // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. + // TODO: set large fields to null. + + disposedValue = true; + } + } + + private IMongoCollection GetCollection() where TEntity : IEntity => + this.database.GetCollection(typeof(TEntity).Name); + + #endregion Private Methods + + // To detect redundant calls + // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. + // ~MongoDataAccessObject() { + // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + // Dispose(false); + // } + } +} diff --git a/src/EdaSample.EventBus.RabbitMQ/RabbitMQEventBus.cs b/src/EdaSample.EventBus.RabbitMQ/RabbitMQEventBus.cs index ed933e2..dca04ca 100644 --- a/src/EdaSample.EventBus.RabbitMQ/RabbitMQEventBus.cs +++ b/src/EdaSample.EventBus.RabbitMQ/RabbitMQEventBus.cs @@ -101,10 +101,17 @@ private string InitializeEventConsumer(string queue) var eventBody = eventArgument.Body; var json = Encoding.UTF8.GetString(eventBody); var @event = (IEvent)JsonConvert.DeserializeObject(json, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }); - await this.eventHandlerExecutionContext.HandleEventAsync(@event); - if (!autoAck) + try { - channel.BasicAck(eventArgument.DeliveryTag, false); + await this.eventHandlerExecutionContext.HandleEventAsync(@event); + if (!autoAck) + { + channel.BasicAck(eventArgument.DeliveryTag, false); + } + } + catch (Exception ex) + { + logger.LogError(ex, "事件处理器执行失败。"); } }; diff --git a/src/EdaSample.Services.Common/EdaSample.Services.Common.csproj b/src/EdaSample.Services.Common/EdaSample.Services.Common.csproj new file mode 100644 index 0000000..9e54432 --- /dev/null +++ b/src/EdaSample.Services.Common/EdaSample.Services.Common.csproj @@ -0,0 +1,11 @@ + + + + netcoreapp2.0 + + + + + + + diff --git a/src/EdaSample.Services.Customer/Events/CustomerCreatedEvent.cs b/src/EdaSample.Services.Common/Events/CustomerCreatedEvent.cs similarity index 57% rename from src/EdaSample.Services.Customer/Events/CustomerCreatedEvent.cs rename to src/EdaSample.Services.Common/Events/CustomerCreatedEvent.cs index 7dfc3bc..868069c 100644 --- a/src/EdaSample.Services.Customer/Events/CustomerCreatedEvent.cs +++ b/src/EdaSample.Services.Common/Events/CustomerCreatedEvent.cs @@ -1,24 +1,27 @@ using EdaSample.Common.Events; using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -namespace EdaSample.Services.Customer.Events +namespace EdaSample.Services.Common.Events { public class CustomerCreatedEvent : IEvent { - public CustomerCreatedEvent(string customerName) + public CustomerCreatedEvent(Guid customerId, string customerName, string email) { this.Id = Guid.NewGuid(); this.Timestamp = DateTime.UtcNow; + this.CustomerId = customerId; this.CustomerName = customerName; + this.Email = email; } public Guid Id { get; } public DateTime Timestamp { get; } + public Guid CustomerId { get; } + public string CustomerName { get; } + + public string Email { get; } } } diff --git a/src/EdaSample.Services.Customer/Controllers/CustomersController.cs b/src/EdaSample.Services.Customer/Controllers/CustomersController.cs index 7c87b73..49eebb3 100644 --- a/src/EdaSample.Services.Customer/Controllers/CustomersController.cs +++ b/src/EdaSample.Services.Customer/Controllers/CustomersController.cs @@ -1,6 +1,6 @@ using Dapper; using EdaSample.Common.Events; -using EdaSample.Services.Customer.Events; +using EdaSample.Services.Common.Events; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Data.SqlClient; using System.Linq; +using System.Net; using System.Threading.Tasks; namespace EdaSample.Services.Customer.Controllers @@ -56,21 +57,23 @@ public async Task Create([FromBody] dynamic model) var name = (string)model.Name; if (string.IsNullOrEmpty(name)) { - return BadRequest(); + return BadRequest("请指定客户名称。"); } - const string sql = "INSERT INTO [dbo].[Customers] ([CustomerId], [CustomerName]) VALUES (@Id, @Name)"; - using (var connection = new SqlConnection(connectionString)) + var email = (string)model.Email; + if (string.IsNullOrEmpty(email)) { - var customer = new Model.Customer(name); - await connection.ExecuteAsync(sql, customer); - - await this.eventBus.PublishAsync(new CustomerCreatedEvent(name)); + return BadRequest("电子邮件地址不能为空。"); + } + + // 由于数据库更新需要通过事件处理器进行异步更新,因此无法在Controller中得到 + // 数据库更新后的Customer ID。此处通过Guid.NewGuid获得,实际中可以使用独立 + // 的Identity Service产生。 + var customerId = Guid.NewGuid(); - this.logger.LogInformation($"客户信息创建成功。"); + await this.eventBus.PublishAsync(new CustomerCreatedEvent(customerId, name, email)); - return Created(Url.Action("Get", new { id = customer.Id }), customer.Id); - } + return Created(Url.Action("Get", new { id = customerId }), customerId); } } } diff --git a/src/EdaSample.Services.Customer/EdaSample.Services.Customer.csproj b/src/EdaSample.Services.Customer/EdaSample.Services.Customer.csproj index 39036bf..2d3a1c3 100644 --- a/src/EdaSample.Services.Customer/EdaSample.Services.Customer.csproj +++ b/src/EdaSample.Services.Customer/EdaSample.Services.Customer.csproj @@ -29,6 +29,7 @@ + diff --git a/src/EdaSample.Services.Customer/EventHandlers/AddNewCustomerEventHandler.cs b/src/EdaSample.Services.Customer/EventHandlers/AddNewCustomerEventHandler.cs new file mode 100644 index 0000000..f55a85b --- /dev/null +++ b/src/EdaSample.Services.Customer/EventHandlers/AddNewCustomerEventHandler.cs @@ -0,0 +1,41 @@ +using Dapper; +using EdaSample.Services.Common.Events; +using EdaSample.Services.Customer.Model; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Data.SqlClient; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace EdaSample.Services.Customer.EventHandlers +{ + public class AddNewCustomerEventHandler : EdaSample.Common.Events.EventHandler + { + private readonly ILogger logger; + private readonly IOptions config; + + public AddNewCustomerEventHandler(ILogger logger, IOptions config, + IConfiguration configuration) + { + this.logger = logger; + this.config = config; + } + + public override async Task HandleAsync(CustomerCreatedEvent @event, CancellationToken cancellationToken = default) + { + const string sql = "INSERT INTO [dbo].[Customers] ([CustomerId], [CustomerName]) VALUES (@Id, @Name)"; + using (var connection = new SqlConnection(this.config.Value.ConnectionString)) + { + var customer = new Model.Customer(@event.CustomerId, @event.CustomerName); + await connection.ExecuteAsync(sql, customer); + this.logger.LogInformation($"客户信息创建成功。"); + } + + return true; + } + } +} diff --git a/src/EdaSample.Services.Customer/EventHandlers/CustomerCreatedEventHandler.cs b/src/EdaSample.Services.Customer/EventHandlers/CustomerCreatedEventHandler.cs index 1d499fa..b95c485 100644 --- a/src/EdaSample.Services.Customer/EventHandlers/CustomerCreatedEventHandler.cs +++ b/src/EdaSample.Services.Customer/EventHandlers/CustomerCreatedEventHandler.cs @@ -1,5 +1,5 @@ using EdaSample.Common.Events; -using EdaSample.Services.Customer.Events; +using EdaSample.Services.Common.Events; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; @@ -9,7 +9,7 @@ namespace EdaSample.Services.Customer.EventHandlers { - public class CustomerCreatedEventHandler : IEventHandler + public class CustomerCreatedEventHandler : EdaSample.Common.Events.EventHandler { private readonly IEventStore eventStore; private readonly ILogger logger; @@ -22,18 +22,12 @@ public CustomerCreatedEventHandler(IEventStore eventStore, this.logger.LogInformation($"CustomerCreatedEventHandler构造函数调用完成。Hash Code: {this.GetHashCode()}."); } - public bool CanHandle(IEvent @event) - => @event.GetType().Equals(typeof(CustomerCreatedEvent)); - - public async Task HandleAsync(CustomerCreatedEvent @event, CancellationToken cancellationToken = default) + public override async Task HandleAsync(CustomerCreatedEvent @event, CancellationToken cancellationToken = default) { this.logger.LogInformation($"开始处理CustomerCreatedEvent事件,处理器Hash Code:{this.GetHashCode()}."); await this.eventStore.SaveEventAsync(@event); this.logger.LogInformation($"结束处理CustomerCreatedEvent事件,处理器Hash Code:{this.GetHashCode()}."); return true; } - - public Task HandleAsync(IEvent @event, CancellationToken cancellationToken = default) - => CanHandle(@event) ? HandleAsync((CustomerCreatedEvent)@event, cancellationToken) : Task.FromResult(false); } } diff --git a/src/EdaSample.Services.Customer/Model/Customer.cs b/src/EdaSample.Services.Customer/Model/Customer.cs index d27b661..69d39e5 100644 --- a/src/EdaSample.Services.Customer/Model/Customer.cs +++ b/src/EdaSample.Services.Customer/Model/Customer.cs @@ -22,6 +22,8 @@ public Customer(Guid id, string name) public Guid Id { get; set; } + public string Email { get; set; } + public string Name { get; set; } } } diff --git a/src/EdaSample.Services.Customer/Model/MssqlConfig.cs b/src/EdaSample.Services.Customer/Model/MssqlConfig.cs new file mode 100644 index 0000000..b1af854 --- /dev/null +++ b/src/EdaSample.Services.Customer/Model/MssqlConfig.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace EdaSample.Services.Customer.Model +{ + public class MssqlConfig + { + public string ConnectionString { get; set; } + } +} diff --git a/src/EdaSample.Services.Customer/Startup.cs b/src/EdaSample.Services.Customer/Startup.cs index 9fdd4a7..c73ea17 100644 --- a/src/EdaSample.Services.Customer/Startup.cs +++ b/src/EdaSample.Services.Customer/Startup.cs @@ -7,8 +7,9 @@ using EdaSample.EventBus.Simple; using EdaSample.EventStores.Dapper; using EdaSample.Integration.AspNetCore; +using EdaSample.Services.Common.Events; using EdaSample.Services.Customer.EventHandlers; -using EdaSample.Services.Customer.Events; +using EdaSample.Services.Customer.Model; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; @@ -22,7 +23,7 @@ namespace EdaSample.Services.Customer public class Startup { private const string RMQ_EXCHANGE = "EdaSample.Exchange"; - private const string RMQ_QUEUE = "EdaSample.Queue"; + private const string RMQ_QUEUE = "EdaSample.CustomerServiceQueue"; private readonly ILogger logger; @@ -39,6 +40,8 @@ public void ConfigureServices(IServiceCollection services) this.logger.LogInformation("正在对服务进行配置..."); services.AddMvc(); + services.AddOptions(); + services.Configure(Configuration.GetSection("mssql")); services.AddTransient(serviceProvider => new DapperEventStore(Configuration["mssql:connectionString"], @@ -64,6 +67,7 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env) { var eventBus = app.ApplicationServices.GetRequiredService(); eventBus.Subscribe(); + eventBus.Subscribe(); if (env.IsDevelopment()) { diff --git a/src/EdaSample.Services.Notification/Controllers/ValuesController.cs b/src/EdaSample.Services.Notification/Controllers/ValuesController.cs new file mode 100644 index 0000000..ed77810 --- /dev/null +++ b/src/EdaSample.Services.Notification/Controllers/ValuesController.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace EdaSample.Services.Notification.Controllers +{ + [Route("api/[controller]")] + public class ValuesController : Controller + { + // GET api/values + [HttpGet] + public IEnumerable Get() + { + return new string[] { "value1", "value2" }; + } + + // GET api/values/5 + [HttpGet("{id}")] + public string Get(int id) + { + return "value"; + } + + // POST api/values + [HttpPost] + public void Post([FromBody]string value) + { + } + + // PUT api/values/5 + [HttpPut("{id}")] + public void Put(int id, [FromBody]string value) + { + } + + // DELETE api/values/5 + [HttpDelete("{id}")] + public void Delete(int id) + { + } + } +} diff --git a/src/EdaSample.Services.Notification/EdaSample.Services.Notification.csproj b/src/EdaSample.Services.Notification/EdaSample.Services.Notification.csproj new file mode 100644 index 0000000..45b2275 --- /dev/null +++ b/src/EdaSample.Services.Notification/EdaSample.Services.Notification.csproj @@ -0,0 +1,28 @@ + + + + netcoreapp2.0 + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/EdaSample.Services.Notification/EventHandlers/CustomerCreatedEventHandler.cs b/src/EdaSample.Services.Notification/EventHandlers/CustomerCreatedEventHandler.cs new file mode 100644 index 0000000..a30ae44 --- /dev/null +++ b/src/EdaSample.Services.Notification/EventHandlers/CustomerCreatedEventHandler.cs @@ -0,0 +1,27 @@ +using EdaSample.Common.Events; +using EdaSample.Services.Common.Events; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace EdaSample.Services.Notification.EventHandlers +{ + public class CustomerCreatedEventHandler : EdaSample.Common.Events.EventHandler + { + private readonly ILogger logger; + + public CustomerCreatedEventHandler(ILogger logger) + { + this.logger = logger; + } + + public override Task HandleAsync(CustomerCreatedEvent @event, CancellationToken cancellationToken = default(CancellationToken)) + { + this.logger.LogInformation("已经成功发送通知消息。"); + return Task.FromResult(true); + } + } +} diff --git a/src/EdaSample.Services.Notification/Program.cs b/src/EdaSample.Services.Notification/Program.cs new file mode 100644 index 0000000..27903b5 --- /dev/null +++ b/src/EdaSample.Services.Notification/Program.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace EdaSample.Services.Notification +{ + public class Program + { + private static readonly string LogFileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "EdaSample.Services.Notification.logs.txt"); + + public static void Main(string[] args) + { + BuildWebHost(args).Run(); + } + + public static IWebHost BuildWebHost(string[] args) => + WebHost.CreateDefaultBuilder(args) + .ConfigureLogging((context, lb) => + { + lb.AddFile(LogFileName); + }) + .UseStartup() + .Build(); + } +} diff --git a/src/EdaSample.Services.Notification/Startup.cs b/src/EdaSample.Services.Notification/Startup.cs new file mode 100644 index 0000000..ee5848a --- /dev/null +++ b/src/EdaSample.Services.Notification/Startup.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using EdaSample.Common.DataAccess; +using EdaSample.Common.Events; +using EdaSample.DataAccess.MongoDB; +using EdaSample.EventBus.RabbitMQ; +using EdaSample.Integration.AspNetCore; +using EdaSample.Services.Common.Events; +using EdaSample.Services.Notification.EventHandlers; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using RabbitMQ.Client; + +namespace EdaSample.Services.Notification +{ + public class Startup + { + private const string RMQ_EXCHANGE = "EdaSample.Exchange"; + private const string RMQ_QUEUE = "EdaSample.NotificationServiceQueue"; + + private readonly ILogger logger; + + public Startup(IConfiguration configuration, ILoggerFactory loggerFactory) + { + Configuration = configuration; + this.logger = loggerFactory.CreateLogger(); + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc(); + + var eventHandlerExecutionContext = new EventHandlerExecutionContext(services, + sc => sc.BuildServiceProvider()); + services.AddSingleton(eventHandlerExecutionContext); + + var connectionFactory = new ConnectionFactory { HostName = "localhost" }; + services.AddSingleton(sp => new RabbitMQEventBus(connectionFactory, + sp.GetRequiredService>(), + sp.GetRequiredService(), + RMQ_EXCHANGE, + queueName: RMQ_QUEUE)); + + var mongoServer = Configuration["mongo:server"]; + var mongoDatabase = Configuration["mongo:database"]; + var mongoPort = Convert.ToInt32(Configuration["mongo:port"]); + services.AddSingleton(serviceProvider => new MongoDataAccessObject(mongoDatabase, mongoServer, mongoPort)); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + var eventBus = app.ApplicationServices.GetRequiredService(); + eventBus.Subscribe(); + + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseMvc(); + } + } +} diff --git a/src/EdaSample.Services.Notification/appsettings.Development.json b/src/EdaSample.Services.Notification/appsettings.Development.json new file mode 100644 index 0000000..006cc15 --- /dev/null +++ b/src/EdaSample.Services.Notification/appsettings.Development.json @@ -0,0 +1,15 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + }, + "mongo": { + "server": "localhost", + "port": "27017", + "database": "EdaSample_NotificationService" + } +} diff --git a/src/EdaSample.Services.Notification/appsettings.json b/src/EdaSample.Services.Notification/appsettings.json new file mode 100644 index 0000000..26bb0ac --- /dev/null +++ b/src/EdaSample.Services.Notification/appsettings.json @@ -0,0 +1,15 @@ +{ + "Logging": { + "IncludeScopes": false, + "Debug": { + "LogLevel": { + "Default": "Warning" + } + }, + "Console": { + "LogLevel": { + "Default": "Warning" + } + } + } +}