队列框架方案之消息通知解决方案

项目实施规范

编号:DEMO-NOTIFY-PROJ

版本:1.0

 


 

 

1概述

在进行系统设计时,除了对安全、事务等问题给与足够的重视外,性能也是一个不可避免的问题所在,尤其是一个B/S结构的软件系统,必须充分地考虑访问量、数据流量、服务器负荷的问题。解决性能的瓶颈,除了对硬件系统进行升级外,软件设计的合理性尤为重要。对于一些实时性不是很高的模块我们可以使用了Microsoft Messaging Queue(MSMQ)技术来完成异步处理,利用消息队列临时存放要操作的数据,使得数据访问因为不需要访问数据库从而提高了访问性能,至于队列中的数据,则等待系统空闲的时候再进行处理。【摘自张逸】

消息通知系统就是利用MSMQ技术、window服务实现一系列的通知服务。

1.1    目的

n         为消息通知提供解决方案

 

n         本文档的编写也为下阶段的设计、开发提供依据,为项目组成员对需求的详尽理解,以及在开发开发过程中的协同工作提供强有力的保证。同时本文档也作为项目评审验收的依据之一。

1.2    范围

消息通知解决方案中包括 消息的发送、接受模块;消息的处理模块;消息通知模块等三大模块。

1.3    参考资料

l         C#高级编程 Wrox[消息队列,window服务]

l         PetShop数据访问层之消息处理

l         敏捷软件开发

l         重构-改善既有代码的设计

l         HeadFirst设计模式

1.4    术语定义

用户:是指技术部人员而非客户或软件的最终购买者。

消息:是由通信的双方所需要传递的信息,它可以是各式各样的媒体,如文本、声音、图象等等。我们这里仅仅指MSMQ的Message

消息体:指专门自己定义的一个超类MessageBase。自定义消息体这样做的好处是,一是相当于对数据进行了简单的加密,二则采用自己定义的格式可以节省通信的传递量

namespace DEMO.Notify.Server
{
    [Serializable]
    public class MessageBase
    {
        /// <summary>
        /// 消息类型
        /// </summary>
        private string messagetype;
        public string MessageType
        {
            get { return messagetype; }
            set { messagetype = value; }
        }
        /// <summary>
        /// 消息内容
        /// </summary>
        private string messagebody;
        public string MessageBody
        {
            get { return messagebody; }
            set { messagebody = value; }
        }
    }
}

 

1.5    概述

 

用户利用本系统提供的接口向消息队列发送消息。Window服务通过轮询监视消息队列的数据,并取出消息,根据消息类型通过工厂创建具体的消息处理器Processor处理消息,然后把消息通过消息投递器Poster发送指定的买家、卖家、客服等。

 

 

 

 

 

2       UML图

2.1          Sequence Diagram

消息队列 延迟消息 消息队列 通知_消息通知

2.2          class Diagram

消息队列 延迟消息 消息队列 通知_消息队列 延迟消息_02

 

3       接口设计

3.1    消息体MessageBase重构

3.1.1   重构过程

自定义消息体的好处很多,比如采用自己定义的格式相当于对数据进行了简单的加密,采用自己定义的格式可以节省通信的传递量等等。

因为我们的系统是为处理订单通知服务的,所以消息体的大概内容应该是订单的类型[订单生成、订单支付成功等],消息内容更确切的讲应为构造发送内容必须的参数[目前一个OrderId就够了]。

其消息体的代码如下:

Serializable]
public class
    {
/// <summary>
///消息类型
/// </summary>
private string
public string
        {
get { return
set { messagetype = value; }
        }
/// <summary>
///消息内容
/// </summary>
private string
public string
        {
get { return
set { messagebody = value; }
        }
    }

说明:1:消息体必须可序列化也就是有Serializable特性

      2:消息体尽量为轻量级内容

3.1.2   UML图

 

3.2    客户端组件重构.

3.2.1   重构过程

这个和PetShop的消息发送、接收是一样滴,就不多说了。

 

3.2.2   UML图

 

3.3    Section节点重构

3.3.1   重构过程

先看看app.config里的一段Section代码

<?xml versinotallow="1.0" encoding="utf-8" ?>
<configuration>

  <configSections>

    <!--消息队列路径-->

    <section name="MSMQClient" type="System.Configuration.SingleTagSectionHandler"/>

    <!--log4net 错误日志-->

    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />

    <!--消息通知系统配置节点-->

    <section name="Notify" type="Demo.Notify.Configuration.NotifySection,Demo.Notify"/>

  </configSections>

  <connectionStrings />

  <MSMQClient MessageQueuePath=".\Private$\demoECMessageQueue"></MSMQClient>

  <appSettings>

    <add key="BatchSize" value="10"  />

    <add key="assembly" value="Demo.Notify"/>

    <add key="OrderDuration" value="2"/>

  </appSettings>

  <log4net>

    <root>

      <level value="DEBUG" />

      <appender-ref ref="RollingLogFileAppender" />

    </root>

    <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">

      <param name="File" value="D:\log\" />

      <param name="AppendToFile" value="true" />

      <param name="MaxSizeRollBackups" value="10" />

      <param name="StaticLogFileName" value="false" />

      <param name="DatePattern" value="yyyyMMdd".log"" />

      <param name="RollingStyle" value="Date" />

      <layout type="log4net.Layout.PatternLayout">

        <param name="ConversionPattern" value="%d  [%t]  %-5p  %c  -  %m%n" />

      </layout>

    </appender>

  </log4net>

  <Notify>

    <ConnectionStrings>

      <add name="" constr=""></add>

    </ConnectionStrings>

    <ClientMails>

      <add name="client1" email="yinguang@staff.demo.com.cn"></add>

    </ClientMails>

    <MessagePoster>

      <MailPoster type="MailSenderType"  domain="smtp.163.com" port="25" userid="" password="" from="" fromname="demo.notify" replayto="yimart@163.com"></MailPoster>

    </MessagePoster>

    <MessageProcessors>

      <add name="OrderCreateMsgProcessor" type="Demo.Notify.Processor.OrderCreateMsgProcessor,Demo.Notify" ></add>

    </MessageProcessors>

    <MessageTypes>

      <add name="OrderCreatedSuccess" processor="OrderCreateMsgProcessor"   Poster="EmailPoster,IMPoster,syslogPoster">

        <!--这里可以添加个性属性-->

        <Parameter>

        </Parameter>

      </add>

    </MessageTypes>

  </Notify>
</configuration

 

上面的Section节点是该系统的纽带!没有它,系统将无法运行,发送器、处理器也成了独立的羔羊。既然很重要,那我们也就重点说说这个流程吧。

l         Notify继承ConfigurationSection它里面有ConnectionStrings、ClientMails、MessageTypes、MessageProcessors、MessageSenders等ConfigurationProperty。

l         ConnectionStrings继承ConfigurationElementCollection它保存了不同数据源的 connectionstring.

l         ClientMails继承 ConfigurationElementCollection, 它保存了常用的客服email。

l         MessageSenders 是个ConfigurationElement , 里面配置Poster需要的信息。比如MailSender保存了Email的domain,userid,pwd,from等相关信息。

l         MessageProcessor 是个ConfigurationElementCollection, 里面的每个ConfigurationElement保存了每个处理器和对应的Asssembly。这样就可以通过下面的代码创建对应的处理器。

TypeType.GetType(processName);

AssemblyAssembly.GetAssembly(type);

Object obj = a.CreateInstance(processName);

returnas IProcessor;

l         MessageTypes 保存了每个消息类型[OrderCreate,OrderPaySucceed等] 的个性特征。我们的系统就是通过MessageTypes里的每个MessageType的Processor去MessageProcessor找对应的处理器的。

        1:name:它就是消息体MessageBase的MessageType属性,当系统从msmq里取到消息后,就会根据name去MessageTypes找和MessageType匹配对应的条目

: 当从MessageTypes里找到对应的条目后,就会通过processor去MessageProcessor去找对应的处理器。

:Poster:它是对应了每个Messagetype[一个MessageType对应一个MessageProcessor]所需要的发送器Poster。多个Poster用逗号隔开。这样我们就可以配置发送器了。

:Parameter:是增加的特性列表。其中Consumer,merchant,Client对应了买家、卖家、客服的内容模板,

guestEmail对应客户的email。

3.3.2   UML图

....

.

3.4    发送器Poster的重构

3.4.1   重构过程

商城的订单状态行为会产生通知,通知的方式目前有Email,sys,IM,他们三个有个共同的方法那就是Post投递方法,我们可以把Email,sys,IM当成三个Poster类,并且都有Post的方法。 代码如下:

publicclass
{
public voidparam1)
    { 
//TODO:Process order and Post
    }
}
publicclass
{
public voidparam2)
    {
//TODO:Process order and post
    }
}
publicclass
{
public voidparam3)
    {
//TODO:Process order and post
    }
}

我们细看上面的代码,按照一般的重构常理应该提出一个基类或接口了。这里用接口更合适些,抽取出来的接口如下:

publicinterface ImsgPoster
{
void post(SenderInfo
    }

备注:SenderInfo是一个超类,用来保存要格式化内容时需要的参数

最后完整的代码如下:

publicclass
{
public void
   { 
//TODO:Process order
    }
}
publicclass
{
public void
    {
//TODO:Process order
    }
}
publicclass
{
public void
    {
//TODO:Process order
    }
}
public interface
    {
void
    }
 
publicclass
{}

3.4.2   UML图

 

3.5    处理器重构

3.5.1   重构过程

假设我们把处理每个订单状态[订单生成、订单支付]的程序叫处理器Processor,每个订单状态都有个处理器,比如订单生成、订单支付对应的处理器为OrderCreateProcessor, OrderPaySucceedProcessor,每个处理器都有Process方法,该方法用来生成买家、卖家、客服等发送对象的内容,并调用Process把通知投递出去。通过上述讨论,代码大概如下:

publicclass
{
public void
    { 
//TODO:Poster
    }
    private SenderInfo ConsumerEditor()
    {
//TODO:构造买家SenderInfo
    }
    private SenderInfo MerchantEditor()
    {
//TODO:构造卖家SenderInfo
    }
    private SenderInfo ClientEditor()
    {
//TODO:构造客服SenderInfo
    }
 
}

OrderPaySucceedProcessor.cs的代码内容和上述代码没有什么区别。我们也应该抽象出一个基类或者接口。因为这里是’is a’的关系,而并非’behave like’的关系,并且抽象出来的目的是为了有共有的功能,而非约定、合同。所以我们选择了抽象类。该抽象类UML图如下:

IProcessor 抽象类有4个属性 ,ClientTemplate,MerchantTemplate,ConsumerTemplate代表了客服、商家、买家的内容模板,Msg是Message类型,它保存了从消息队列取的消息条目信息;AddPoster方法向发送器列表里添加了一个发送器,AddSender向发送对象列表里添加了一个SenderInfo[这个SenderInfo是派生类中ConsumerEditor, MerchantEditor, ClientEditor方法构造出来的.],IntialProcessor用来初始化处理器Processor。IProcessor完整的代码如下:

publicabstract class
{
private
    #region templates variables
//Processor通过模板生成不同发送对象的内容
private string _consumertemplate;//买家模板
private string _merchanttemplate;//卖家模板
private string _clienttemplate;//客服模板
    #endregion
private List<SenderInfo> senderlist = new List<SenderInfo>();//发送对象列表
private List<ImsgPoster> posterlist = new List<ImsgPoster>();//发送器列表
    #region template properties
public string
    {
get { return
set { _clienttemplate = value; }
    }
public string
    {
get { return
set { _merchanttemplate = value; }
    }
public string
    {
get { return
set { _consumertemplate = value; }
    }
    #endregion
 
/// <summary>
///初始化Poster[发送器],Template[模板文件]
/// </summary>
protected void
    {
"Notify");
//获取消息类型
string
        msgType = ((MessageBase)msg.Body).MessageType.ToString();
//获取发送器列表,以逗号隔开!
string
string[] posters = poster.Split(new char[] { ','
foreach (string pst in
        {
            AddPoster(DataAccess.CreateMsgPoster(pst));
        }
"Merchant"].ParamValue.ToString();
"Consumer"].ParamValue.ToString();
"Client"].ParamValue.ToString();
 
    }
/// <summary>
///消息
/// </summary>
public
    {
get { return
set { msg = value; }
    }
/// <summary>
///添加SenderInfo(发送对象)
/// </summary>
/// <param name="sender">发送对象</param>
protected void
    {
        senderlist.Add(sender);
    }
 
/// <summary>
///添加发送器
/// </summary>
/// <param name="poster">发送器:Email,Msn,Qq,Yahoo,Sms</param>
public void
    {
        posterlist.Add(poster);
    }
 
/// <summary>
///这个是最关键的地方,用virutal实现多态,从而减少发送器和发送对象的耦合性!~..~
/// </summary>
public virtual void
    {
if (senderlist != null)
        {
for (int
            {
//senderlist[i];
foreach (ImsgPoster poster in
                {
                    poster.post(senderlist[i]);
                }
            }
        }
    }
 
}

现在OrderCreateMsgProcessor类代码如下:

publicclass
{
private
    {   }
private
    {   }
private
    {}
/// <summary>
///实现父类的Process 方法。
/// </summary>
public override void
    {
//initial processor
base.InitialProcessor();
        
//添加买家到链表中~。。~
        SenderInfo sender;
        sender = ECConsumerEditor();
if (sender != null)
        {
base.AddSender(sender);
        }
//添加商家到链表中·。。
        sender = MerchantEditor();
if (sender != null)
        {
base.AddSender(sender);
        }
//添加客服到链表中~。。~
        sender = ClientEditor();
if (sender != null)
        {
base.AddSender(sender);
        }
//邮递员发送到各个地方!
base.Process();
    }
 
}

在IProcessor的InitialProcessor里有个NotifySection对象,这个对象将在3.3的配置节点重构章节里说明。

3.5.2   UML图

消息队列 延迟消息 消息队列 通知_UML_03

 

 

 

4       实例演示下载

demo.notify.rar