• WCF基于MSMQ的事件代理服务


    前言

    公司目前楼主负责的项目正在改版升级,对之前的服务也在作调整,项目里有个操作日志的模块,就决定把日志单独提取出来,做个日志服务,所以就有了这篇文章

    正文

    MSMQ作为消息队列,B/S项目调用日志服务,日志服务往消息队列发送消息,事件代理服务负责处理消息队列的消息,贴下核心代码

    事件代理服务契约

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.ServiceModel;
    using System.Text;
    using TestService.Contract.Faults;
    
    namespace TestService.Contract
    {
        /// <summary>
        /// 事件代理
        /// </summary>
        [ServiceContract(Namespace = "http://TestService.Contract",
                         SessionMode = SessionMode.Required,
                         CallbackContract = typeof(IEventBrokerCallback))]
        public interface IEventBroker
        {
            [OperationContract(IsOneWay = false)]
            [FaultContract(typeof(EventBrokerException))]
            void Subscribe(Guid subscriptionId, string[] eventNames);
    
            [OperationContract(IsOneWay = true)]
            void EndSubscription(Guid subscriptionId);
        }
    }

    事件代理服务回调处理契约

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.ServiceModel;
    using System.Text;
    using TestService.Contract.Data;
    
    namespace TestService.Contract
    {
        /// <summary>
        /// 事件代理回调
        /// </summary>
        public interface IEventBrokerCallback
        {
            [OperationContract(IsOneWay = true)]
            void ReceiveStreamingResult(RealTimeEventMessage streamingResult);
        }
    }

    事件代理服务异常实体

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.Serialization;
    using System.Text;
    
    namespace TestService.Contract.Faults
    {
        [DataContract]
        public class EventBrokerException
        {
            [DataMember]
            public string Message
            {
                get;
                set;
            }
    
            public EventBrokerException(string message)
            {
                Message = message;
            }
        }
    }

    消息处理实体

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.Serialization;
    using System.Text;
    
    namespace TestService.Contract.Data
    {
        /// <summary>
        /// 数据实体
        /// </summary>
        [DataContract]
        public class RealTimeEventMessage
        {
    
            public RealTimeEventMessage()
            {
    
            }
    
            public RealTimeEventMessage(MessageModel msg, string eventName, string entityIdType,
                string description, string additionalData, DateTime date)
            {
                this.Msg = msg;
                this.EventName = eventName;
                this.EntityIdType = entityIdType;
                this.Description = description;
                this.AdditionalData = additionalData;
                this.Date = date;
            }
    
            [DataMember]
            public MessageModel Msg { get; set; }
    
            [DataMember]
            public string EventName { get; set; }
    
            [DataMember]
            public string EntityIdType { get; set; }
    
            [DataMember]
            public string Description { get; set; }
    
            [DataMember]
            public string AdditionalData { get; set; }
    
            [DataMember]
            public DateTime? Date { get; set; }
        }
    }

    以上是事件代理服务的契约部分,下面看看实现,先看EventBroker的实现

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Messaging;
    using System.ServiceModel;
    using System.Threading.Tasks;
    using TestService.ApplicationService.Data;
    using TestService.ApplicationService.Services.Interface;
    using TestService.Common.IoC;
    using TestService.Contract;
    using TestService.Contract.Data;
    using TestService.Contract.Faults;
    
    namespace TestService.ApplicationService
    {
        [IocServiceBehavior]
        [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
        public class EventBroker : ApplicationBaseService, IEventBroker
        {
            Dictionary<string, List<UniqueCallbackHandle>> eventNameToCallbackLookups = new Dictionary<string, List<UniqueCallbackHandle>>();
            private static Object syncObj = new Object();
            private static string inputQueueName = "";
            private bool shouldRun = true;
            private static readonly TimeSpan queueReadTimeOut = TimeSpan.FromSeconds(500);
            private static readonly TimeSpan queuePeekTimeOut = TimeSpan.FromSeconds(30);
            private IXmlParserService _xmlParserService;
    
            public EventBroker(IXmlParserService xmlParserService)
            {
                inputQueueName = AppSettings.InputQueueName;
                StartCollectingMessage();
                _xmlParserService = xmlParserService;
            }
    
            public void StartCollectingMessage()
            {
                try
                {
                    GetMessageFromQueue();
                }
                catch (Exception ex)
                {
                    throw new FaultException<EventBrokerException>(new EventBrokerException(ex.Message), new FaultReason(ex.Message));
                }
            }
    
            public void Subscribe(Guid subscriptionId, string[] eventNames)
            {
                try
                {
                    CreateSubscription(subscriptionId, eventNames);
                }
                catch (Exception ex)
                {
                    throw new FaultException<EventBrokerException>(new EventBrokerException(ex.Message), new FaultReason(ex.Message));
                }
            }
    
            public void EndSubscription(Guid subscriptionId)
            {
                lock (syncObj)
                {
                    //create new dictionary that will be populated by those remaining
                    Dictionary<string, List<UniqueCallbackHandle>> remainingEventNameToCallbackLookups =
                        new Dictionary<string, List<UniqueCallbackHandle>>();
    
                    foreach (KeyValuePair<string, List<UniqueCallbackHandle>> kvp in eventNameToCallbackLookups)
                    {
                        //get all the remaining subscribers whos session id is not the same as the one we wish to remove
                        List<UniqueCallbackHandle> remainingMessageSubscriptions =
                            kvp.Value.Where(x => x.CallbackSessionId != subscriptionId).ToList();
                        if (remainingMessageSubscriptions.Any())
                        {
                            remainingEventNameToCallbackLookups.Add(kvp.Key, remainingMessageSubscriptions);
                        }
                    }
                    //now left with only the subscribers that are subscribed
                    eventNameToCallbackLookups = remainingEventNameToCallbackLookups;
                }
            }
    
            #region 私有方法
    
            /// <summary>
            /// 从消息队列中获取消息
            /// </summary>
            private void GetMessageFromQueue()
            {
                try
                {
                    Task messageQueueReaderTask = Task.Factory.StartNew(() =>
                    {
                        using (MessageQueue queue = new MessageQueue(inputQueueName, QueueAccessMode.Receive))
                        {
    
                            queue.Formatter = new XmlMessageFormatter(new[] { typeof(string) });
    
                            while (shouldRun)
                            {
                                Message message = null;
    
                                try
                                {
                                    if (!queue.IsEmpty())
                                    {
                                        //Logs.Debug("接受队列里的消息");
                                        message = queue.Receive(queueReadTimeOut);
                                        ProcessMessage(message);
                                    }
                                }
                                catch (MessageQueueException e)
                                {
                                    if (e.MessageQueueErrorCode != MessageQueueErrorCode.IOTimeout)
                                    {
                                        Log.Warn("消息队列出现异常:", e);
                                    }
                                }
                                catch (Exception e)
                                {
                                    // Write the message details to the Error queue
                                    Log.Warn("操作异常:", e);
                                }
                            }
                        }
                    }, TaskCreationOptions.LongRunning);
                }
                catch (AggregateException ex)
                {
                    throw;
                }
            }
    
            /// <summary>
            /// 处理消息
            /// </summary>
            /// <param name="msmqMessage"></param>
            private void ProcessMessage(Message msmqMessage)
            {
                string messageBody = (string)msmqMessage.Body;
    
    #if DEBUG
                Log.Info(string.Format("接受消息 : {0}", messageBody));
    #endif
    
                RealTimeEventMessage messageToSendToSubscribers = _xmlParserService.ParseRawMsmqXml(messageBody);
                if (messageToSendToSubscribers != null)
                {
                    lock (syncObj)
                    {
                        if (messageToSendToSubscribers.Msg != null)
                        {
                            // 保存到数据库
                            
                        }
    
                        List<Guid> deadSubscribers = new List<Guid>();
    
                        if (eventNameToCallbackLookups.ContainsKey(messageToSendToSubscribers.EventName))
                        {
                            List<UniqueCallbackHandle> uniqueCallbackHandles =
                                eventNameToCallbackLookups[messageToSendToSubscribers.EventName];
                            foreach (UniqueCallbackHandle uniqueCallbackHandle in uniqueCallbackHandles)
                            {
                                try
                                {
                                    uniqueCallbackHandle.Callback.ReceiveStreamingResult(messageToSendToSubscribers);
    
                                }
                                catch (CommunicationObjectAbortedException coaex)
                                {
                                    deadSubscribers.Add(uniqueCallbackHandle.CallbackSessionId);
                                }
                            }
                        }
    
                        //end all subcriptions for dead subscribers
                        foreach (Guid deadSubscriberId in deadSubscribers)
                        {
                            EndSubscription(deadSubscriberId);
                        }
                    }
                }
            }
    
            private void CreateSubscription(Guid subscriptionId, string[] eventNames)
            {
                //Ensure that a subscription is created for each message type the subscriber wants to receive
                lock (syncObj)
                {
                    foreach (string eventName in eventNames)
                    {
                        if (!eventNameToCallbackLookups.ContainsKey(eventName))
                        {
                            List<UniqueCallbackHandle> currentCallbacks = new List<UniqueCallbackHandle>();
                            eventNameToCallbackLookups[eventName] = currentCallbacks;
                        }
                        eventNameToCallbackLookups[eventName].Add(
                            new UniqueCallbackHandle(subscriptionId, OperationContext.Current.GetCallbackChannel<IEventBrokerCallback>()));
                    }
                }
            }
    
            #endregion
        }
    }

    事件代理实现里的回调处理实体

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using TestService.Contract;
    
    namespace TestService.ApplicationService.Data
    {
        public class UniqueCallbackHandle
        {
            public UniqueCallbackHandle(Guid callbackSessionId, IEventBrokerCallback callback)
            {
                this.CallbackSessionId = callbackSessionId;
                this.Callback = callback;
            }
    
            public Guid CallbackSessionId { get; private set; }
    
            public IEventBrokerCallback Callback { get; private set; }
        }
    }

    其中事件代理服务构造方法的AppSettings.InputQueueName是读取配置文件里的专用队列名称,这里有个构造方法,后面会使用Autofac进行注入,另外还有个IXmlParserService普通业务操作的,主要是解析消息队列里存放的xml消息

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using TestService.Contract.Data;
    
    namespace TestService.ApplicationService.Services.Interface
    {
        public interface IXmlParserService
        {
            RealTimeEventMessage ParseRawMsmqXml(string messageBody);
        }
    }

    实现

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Xml.Linq;
    using TestService.ApplicationService.Services.Interface;
    using TestService.Contract.Data;
    
    namespace TestService.ApplicationService.Services.Impl
    {
        public class XmlParserService : IXmlParserService
        {
            private MessageModel msg;
    
            public XmlParserService(MessageModel msg)
            {
                this.msg = msg;
            }
    
            public RealTimeEventMessage ParseRawMsmqXml(string messageBody)
            {
                try
                {
                    RealTimeEventMessage info = new RealTimeEventMessage();
                    XElement xelement = XElement.Parse(messageBody);
                    MessageModel model = new MessageModel();
                    model.MessageId = GetSafeString(xelement, "messageId");
                    model.Content = GetSafeString(xelement, "content");
                    model.CreateTime = GetSafeDate(xelement, "createTime") ?? DateTime.Now;
    
                    info.Msg = model;
                    info.EventName = GetSafeString(xelement, "eventName");
                    //info.EntityIdType = GetSafeString(xelement, "entityIdType");
                    //info.Description = GetSafeString(xelement, "description").Replace("
    
    ", "
    
    ");
                    //info.Date = GetSafeDate(xelement, "date");
                    //info.AdditionalData = GetSafeString(xelement, "additionalData");
                    return info;
    
                }
                catch (Exception ex)
                {
                    Log.Error("解析Xml消息出现异常:" + ex);
                    return null;
                }
            }
    
            public static Int32 GetSafeInt32(XElement root, string elementName)
            {
                try
                {
                    XElement element = root.Elements().Where(node => node.Name.LocalName == elementName).Single();
                    return Convert.ToInt32(element.Value);
                }
                catch
                {
                    return 0;
                }
            }
    
            private static DateTime? GetSafeDate(XElement root, string elementName)
            {
                try
                {
                    XElement element = root.Elements().Where(node => node.Name.LocalName == elementName).Single();
                    return DateTime.Parse(element.Value);
                }
                catch
                {
                    return null;
                }
            }
    
            public static String GetSafeString(XElement root, string elementName)
            {
                try
                {
                    XElement element = root.Elements().Where(node => node.Name.LocalName == elementName).Single();
                    return element.Value;
                }
                catch
                {
                    return String.Empty;
                }
            }
    
            public static bool GetSafeBool(XElement root, string elementName)
            {
                try
                {
                    XElement element = root.Elements().Where(node => node.Name.LocalName == elementName).Single();
                    return Convert.ToBoolean(element.Value);
                }
                catch
                {
                    return false;
                }
            }
        }
    }

    这里的xml节点主要是根据消息服务里发送消息的xml节点来定的,事件代理服务的就是上面的这么多,下面看看消息服务,

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.ServiceModel;
    using System.Text;
    using TestService.Contract.Data;
    
    namespace TestService.Contract
    {
        [ServiceContract]
        public interface IMessageService
        {
            [OperationContract]
            bool Send(MessageModel model);
        }
    }

    实现

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Messaging;
    using System.Text;
    using TestService.Common;
    using TestService.Common.IoC;
    using TestService.Contract;
    using TestService.Contract.Data;
    
    namespace TestService.ApplicationService
    {
        /// <summary>
        /// 消息服务
        /// </summary>
        [IocServiceBehavior]
        public class MessageService : ApplicationBaseService, IMessageService
        {
            private static string inputQueueName = "";
    
            public MessageService()
            {
                inputQueueName = AppSettings.InputQueueName;
            }
    
            public bool Send(MessageModel model)
            {
                using (MessageQueue queue = new MessageQueue(inputQueueName, QueueAccessMode.Send))
                {
                    queue.Formatter = new XmlMessageFormatter(new[] { typeof(string) });
                    try
                    {
                        Message message = new Message(
                            GetXmlData(model));
                        Log.Info(string.Format("发送消息: {0}", message.Body.ToString()));
                        queue.Send(message);
    
                        return true;
                    }
                    catch (MessageQueueException mex)
                    {
                        if (mex.MessageQueueErrorCode != MessageQueueErrorCode.IOTimeout)
                        {
                            Log.Error(string.Format("Message queue exception occured", mex));
                        }
                        return false;
                    }
                    catch (Exception ex)
                    {
                        // Write the message details to the Error queue
                        Log.Error(ex);
                        return false;
                    }
                }
            }
    
            private string GetXmlData(MessageModel model)
            {
                StringBuilder sb = new StringBuilder("<realtimeEvent>");
                sb.AppendFormat("<eventName>ClientDealEvent</eventName>");
                sb.AppendFormat("<messageId>{0}</messageId>", model.MessageId);
                sb.AppendFormat("<createTime>{0}</createTime>", model.CreateTime);
                sb.AppendFormat("<content>{0}</content>", model.Content);
                sb.AppendLine("</realtimeEvent>");
                return sb.ToString();
            }
        }
    }

    消息服务比较简单,就是往消息队列里发送消息,细心的人会发现,我在每个服务实现里都加了个IocServiceBehavior特性,这个主要是标识了注入了该服务

    核心代码就是上面介绍的那么多,有些操作没将代码贴出来,后面会将代码开源到码云上,今天就先写到这儿了

  • 相关阅读:
    字典树Trie
    转载一个不错的LRU cache
    git和github基础入门
    git基础之常用操作
    python矩阵和向量的转置问题
    梯度下降法注意要点
    python 浮点数问题
    Python数据分析基础——读写CSV文件2
    Python数据分析基础——读写CSV文件
    读书笔记----javascript函数编程
  • 原文地址:https://www.cnblogs.com/zhouxiaoyun/p/5816357.html
Copyright © 2020-2023  润新知