[ELMA3] Создание собственного канала отправки сообщений
В статье приведено два примера создания собственного канала отправки сообщений:
- Создание сообщений отдельными текстовыми файлами на сервере.
- Запись сообщений в базу данных.
В данном примере все сообщения системы (отправка сообщений, оповещения о просрочке задач, активация задач и так далее) будут отправляться через Ваш собственный канал сообщений. С помощью данной точки расширения можно реализовать канал отправки сообщений в Twitter, ICQ, Jabber при необходимости.
Пример отображения данных
Рис. 1. Создание сообщений отдельными текстовыми файлами на сервере
Рис. 2. Запись сообщений в БД
Методы расширения (интерфейса)
Точка расширения (интерфейс) IMessageChannel имеет следующие методы:
/// <summary> /// Уникальный идентификатор канала /// </summary> Guid Uid { get; } /// <summary> /// Имя канала /// </summary> string Name { get; } /// <summary> /// Имя для отображения /// </summary> string DisplayName { get; } /// <summary> /// Использовать по умолчанию /// </summary> bool Default { get; } /// <summary> /// Отправить сообщение /// </summary> /// <param name="message">Сообщение</param> void Send(IMessage message);
Пример класса точки расширения
Создание сообщений отдельными текстовыми файлами на сервере.
[Component] public class MessageChannel : IMessageChannel { private readonly Guid _uid = new Guid("{B2D745F9-9624-40c4-9C07-ABF44281F066}"); public Guid Uid { get { return _uid; } } public string Name { get { return "TextFileChannel"; } } public string DisplayName { get { return "Канал отправки сообщений в текстовые файлы"; } } public bool Default { get { return true; } } private static readonly string Filepath = Locator.GetServiceNotNull<IRuntimeApplication>().Configuration.Config.FilePath; private static readonly string Fullpath = Path.GetDirectoryName(Filepath); private static readonly string HeadDir = Path.Combine(Fullpath, "Messages"); public void Send(IMessage message) { //Проверка на наличие сообщения if (message == null) throw new ArgumentNullException("message"); //Проверка получателя var recipient = message.Recipient as IUser; if (recipient == null) { return; } string recipientDir = Path.Combine(headDir, recipient.ToString()); if (!Directory.Exists(headDir)) Directory.CreateDirectory(headDir); if (!Directory.Exists(recipientDir)) Directory.CreateDirectory(recipientDir); string path = Path.Combine(recipientDir, string.Format("{0}_{1}-{2}-{3}.{4}.txt", DateTime.Now.ToShortDateString(), DateTime.Now.Hour, DateTime.Now.Minute, DateTime.Now.Second, DateTime.Now.Millisecond)); if (!File.Exists(path)) { using (StreamWriter file1 = new StreamWriter(path, true)) { file1.WriteLine("Тема: {0}\r\n Текст сообщения: {1}", message.Subject, message.FullMessageText); } } } }
Запись сообщений в базу данных.
[Component] public class MessageChannelDB : IMessageChannel { private readonly Guid _uid = new Guid("{FA1B0A61-B3F6-4f16-A57F-9D6253710D50}"); public Guid Uid { get { return _uid; } } public string Name { get { return "DBChannelMessage"; } } public string DisplayName { get { return "Канал отправки сообщений в БД"; } } public bool Default { get { return true; } } public void Send(IMessage message) { //Проверка на наличие сообщения if (message == null) throw new ArgumentNullException("message"); //Проверка получателя var recipient = message.Recipient as IUser; if (recipient == null) { return; } var author = message.Author as IUser; if (author == null) { return; } Dictionary<string, object> parameters = new Dictionary<string, object>(); parameters.Add("SUBJECT", message.Subject); parameters.Add("RECIPIENT", recipient.Id); parameters.Add("TEXT", message.FullMessageText); parameters.Add("AUTHOR", author.Id); FireBirdConnection.SqlQuery("insert into MESSAGES (ID, SUBJECT, RECIPIENT, TEXT, \"DATE\", AUTHOR) values (gen_id(GEN_MESSAGES_ID, 1), @SUBJECT, @RECIPIENT, @TEXT, current_timestamp, @AUTHOR)", parameters); } }
В данном примере формируется запрос в базу данных FireBird, который добавляет новую запись в таблицу MESSAGES. Для реализации подключения к базе данных FireBird и создания запроса в базу данных был создан класс FireBirdConnection.cs.
Код класса FireBirdConnection.cs:
using System; using System.Collections.Generic; using System.Data; using System.IO; using EleWise.ELMA.Logging; using EleWise.ELMA.Runtime; using EleWise.ELMA.Services; using FirebirdSql.Data.FirebirdClient; namespace MessageChannel.Connection { public static class FireBirdConnection { private static readonly string Filepath = Locator.GetServiceNotNull<IRuntimeApplication>().Configuration.Config.FilePath; //Путь до файла конфигурации private static readonly string HeadDir = Path.GetDirectoryName(Filepath); //Директория файла конфигурации private const string DbName = "BASEMESSAGES.FDB"; //Имя базы данных //Формируем строку подключения private static readonly string Fbconnection = new FbConnectionStringBuilder { DataSource = "127.0.0.1", UserID = "sysdba", Password = "masterkey", Port = 3056, Dialect = 3, ServerType = 0, Database = Path.Combine(HeadDir, DbName), Charset = "UNICODE_FSS" }.ToString(); private readonly static FbConnection Fb = new FbConnection(Fbconnection); public static void SqlQuery(string query, Dictionary<string, object> parameters = null) { if (Fb.State == ConnectionState.Closed) //если соединение закрыто - откроем его Fb.Open(); //Создаем запрос using (var fbCommand = new FbCommand(query, Fb)) { var fbt = Fb.BeginTransaction(); if (parameters != null) { foreach (var items in parameters) { fbCommand.Parameters.AddWithValue(items.Key, items.Value); } } fbCommand.Transaction = fbt; try { fbCommand.ExecuteNonQuery(); //для запросов, не возвращающих набор данных (insert, update, delete) надо вызывать этот метод fbt.Commit(); //если вставка прошла успешно - коммитим транзакцию } catch (Exception exception) { Logger.Log.Error(exception.Message); fbt.Rollback(); } } } } }
В данном примере использовалась база данных FireBird, которую необходимо создать, добавить в неё новую таблицу MESSAGES с полями: ID, SUBJECT, RECIPIENT, TEXT, DATE, AUTHOR. Поле ID необходимо сделать Primary Key и NotNull, а также создать генератор (в примере генератор имеет имя GEN_MESSAGES_ID).
Скрипт создания таблицы в БД FireBird:
CREATE TABLE MESSAGES ( ID BIGINT NOT NULL, SUBJECT VARCHAR(255), RECIPIENT INTEGER, TEXT VARCHAR(255), "DATE" TIMESTAMP, AUTHOR INTEGER ); ALTER TABLE MESSAGES ADD PRIMARY KEY (ID);
В следующем примере показано как создать свой канал сообщений с несколькими получателями. Сообщения будут отправляться в базу данных одной транзакцией.
В данном примере все сообщения системы (отправка сообщений, оповещения о просрочке задач, активация задач и так далее) будут отправляться через Ваш собственный канал сообщений. При необходимости с помощью данной точки расширения можно реализовать канал отправки сообщений в Twitter, ICQ, Jabber.
В данном примере реализована точка расширения IGroupingMessageChannel, в которой есть метод public void Send(IMessage message, IEnumerable<EleWise.ELMA.Security.IUser> recipients), позволяющий отправлять сообщения всем получателям сразу. Это позволяет сократить время на отправку сообщений.
Пример отображения данных
Рис. 1. Запись сообщений в БД
Методы расширения (интерфейса)
Точка расширения (интерфейс) IGroupingMessageChannel имеет следующие методы:
/// <summary> /// Уникальный идентификатор канала /// </summary> Guid Uid { get; } – генерировать Uid для своего канала отправки сообщений нужно самостоятельно. /// <summary> /// Имя канала /// </summary> string Name { get; } /// <summary> /// Имя для отображения /// </summary> string DisplayName { get; } /// <summary> /// Использовать по умолчанию /// </summary> bool Default { get; } /// <summary> /// Отправить сообщение /// </summary> /// <param name="message">Сообщение</param> void Send(IMessage message); /// <summary> /// Отправить сообщение сразу нескольким получателям /// </summary> /// <param name="message">Сообщение</param> /// <param name="recipients">Список пользователей - получателей сообщения</param> void Send(IMessage message, IEnumerable<IUser> recipients);
Пример класса точки расширения
Запись сообщений в базу данных.
[Component] public class GroupingMessageChannel : IGroupingMessageChannel { private readonly Guid _uid = new Guid("{F2E1B073-DADA-4b13-805C-FE2CE3FA9375}"); public Guid Uid { get { return _uid; } } public string Name { get { return "DBGropingMessageChannel"; } } public string DisplayName { get { return "Канал отправки сообщений в БД одной транзакцией"; } } public bool Default { get { return true; } } public void Send(IMessage message) { var recipient = message.Recipient as IUser; if (recipient == null) { return; } Send(message, new[] {recipient}); } public void Send(IMessage message, IEnumerable<EleWise.ELMA.Security.IUser> recipients) { //Проверка на наличие сообщения if (message == null) throw new ArgumentNullException("message"); var recUsers = recipients as IUser[] ?? recipients.ToArray(); if (recUsers.Length == 0) return; var author = message.Author as IUser; if (author == null) { return; } Dictionary<string, object> parameters = new Dictionary<string, object>(); parameters.Add("SUBJECT", message.Subject); parameters.Add("TEXT", message.FullMessageText); parameters.Add("AUTHOR", author.Id); string query = string.Empty; foreach (IUser recipient in recUsers) { query += string.Format( "insert into MESSAGES (SUBJECT, RECIPIENT, TEXT, DATE, AUTHOR) values (@SUBJECT, {0}, @TEXT, current_timestamp, @AUTHOR) ", recipient.Id); } MSSQLConnection.SqlQuery(query, parameters); } }
Запись сообщений в базу данных
В данном примере формируется запрос в базу данных MSSQL, который добавляет несколько записей в одной транзакции в таблицу MESSAGES. Для реализации подключения к базе данных MSSQL и создания запроса в базу данных был создан класс MSSQLConnection.cs.
Код класса MSSQLConnection.cs:
using System; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using EleWise.ELMA.Logging; namespace MessageChannel.Connection { public static class MssqlConnection { //Формируем строку подключения private static readonly string MssqlconnectionString = new SqlConnectionStringBuilder { DataSource = "(local)", UserID = "sa", Password = "p@ssworD", InitialCatalog = "BASEMESSAGES" }.ToString(); private static readonly SqlConnection SqlConnection = new SqlConnection(MssqlconnectionString); public static void SqlQuery(string query, Dictionary<string, object> parameters = null) { if (SqlConnection.State == ConnectionState.Closed) //если соединение закрыто - откроем его SqlConnection.Open(); //Создаем запрос using (var sqlCommand = new SqlCommand(query, SqlConnection)) { var transaction = SqlConnection.BeginTransaction(); if (parameters != null) { foreach (var items in parameters) { sqlCommand.Parameters.AddWithValue(items.Key, items.Value); } } sqlCommand.Transaction = transaction; try { sqlCommand.ExecuteNonQuery(); //для запросов, не возвращающих набор данных (insert, update, delete) надо вызывать этот метод transaction.Commit(); //если вставка прошла успешно - коммитим транзакцию } catch (Exception exception) { Logger.Log.Error(exception.Message); transaction.Rollback(); } } } } }
В данном примере использовалась база данных MSSQL, которую необходимо создать, скрипт создания таблицы в базе данных BASEMESSAGES:
CREATE TABLE [BASEMESSAGES].[dbo].[MESSAGES]( [ID] [bigint] IDENTITY(1,1) NOT NULL, [SUBJECT] [nvarchar](255) NULL, [RECIPIENT] [bigint] NULL, [TEXT] [nvarchar](255) NULL, [AUTHOR] [bigint] NULL, [DATE] [datetime] NULL, CONSTRAINT [PK_MESSAGES] PRIMARY KEY CLUSTERED ( [ID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY]