我生如山

一个小型CRM系统的设计(未完成版本)

Core-Man System Design Document Version 1.0 9/29/2009 Contents 简介 4 系统总体架构介绍 4 系统具体设计 6 后记 6 简介 本文档用来描述Core-Man这个用来为汽配行业做货物、订单、客户关系管理的系统功能和设计。本文档基于该系统的需求分析和前期市场调研,关于需求分析和市场调研的结果,可以查看文档XXXX. 1. 系统设计目标 为了使得系统能最大程度的满足用户的需求,尽可能的减少以后再定制维护过程中的代价,Core-Man系统应该达到以下几个目标:  可定制性:虽然本系统是为了汽配行业的客户管理而设计的,但是本系统需要达到在特定的需求下经过适当的配置(数据库配置,配置文件更改)而达到满足其他行业需求的目的。  可维护性:本系统的设计应该达到可维护的目的,因为用户的需求可能会变化(企业规模扩大,经营业务增多)。在用户变化需求的时候,我们的系统就需要升级,那么在升级的过程,至少需要满足两方面的可重用,一是数据上的可重用。客户在一定的经营过程中积累了一定数目的用户资源和经营经验数据,这部分数据对于客户以后继续经营,扩大业务规模都是有重要作用的。在我们的系统升级的过程,应该使得客户可以重用这部分数据,即使得升级后的系统能够识别维护旧的数据。另一方面的可重用性是指代码上的可重用性,系统的升级不能使推到系统重来。新系统应该基于旧系统已有的代码,这样可以最大程度上的节省成本。  安全性:虽然在系统初期,客户对于安全性上的要求不会很高,但是随着企业业务规模的扩大,必然涉及到很多的商业保密信息。企业的客户数据,订单数据,管理模式都有很重要的商业价值,此时对于我们系统的安全性就提出了要求。Core-Man系统的安全性主要体现在一下几个方面:  用户认证:系统应该区分登录的会话中用户的身份,以此来判断用户可访问哪些数据,可更改哪些数据,这个对于一个中大型企业里面,各个管理层面(老板,总经理,经理,员工)应用同一个系统式很重要的。  权限管理:验证用户后,需要判断已验证用户是否被允许访问其所请求的数据,在判断通过的情况下,系统返回正确的响应;否则,系统告知客户端无权访问。  数据加密:企业的客户数据,员工数据,订单数据都涉及到商业秘密。这些数据都需要经过加密后存储在系统的文件系统或者数据库中。 系统总体架构介绍 很明显的Core-Man系统是一个以数据库为中心,以多终端进行数据处理维护的分布式系统,一个Core-Man系统的典型的应用场景可以用下图来描述: 在上面的应用场景中,各个角色的分工如下:  中心数据库:负责提供整个系统的数据的存贮,这里包含两部分内容  由数据库管理系统托管的数据库表,视图,存储过程,数据等信息。  由文件系统托管的图片,文件等信息。  请求监听/代理服务器:负责作为终端用户和数据库之间通信的一个代理人的角色。所有客户端的操作如果涉及到数据库查询/更改请求,由请求监听/代理服务器将其转换为对相应数据库项目的查询/更新操作。这样的设计能使得终端用户和数据库的耦合性降到最低,使得在需要更换数据库的时候,我们只需要更新请求监听/代理服务器里面与数据库交互的那部分代码。同时,加进一层请求监听/代理服务器,可以让我们的系统在这一层进行安全性上的检查更改等操作,比如,在这一层做终端用户身份的鉴别,只有终端用户是授权用户的时候,我们才允许客户端进行后续操作。  超级管理终端:这个终端有最大的管理权限,用于进行数据的备份,紧急模式下的更改等操作。  分布式终端1:本系统众多分布式终端的其中一个,用户为普通用户(经理,员工,客户)提供访问我们系统的功能。每一个分布式终端在发起连接请求监听/代理服务器的时候,都需要提供用户身份的信息,请求监听/代理服务器用这些信息来判断发起连接的用户是否是一个经过授权的用户。 下图描述了我们的数据库设计的基本轮廓: 下图描述了我们的请求监听/代理服务器的设计模块以及模块之间的联系: 自上至下,三个大的模块分别是:  请求监听层:负责监听客户端的操作请求,检查客户端的身份,以及客户端的权限。如果客户端是经授权的用户并且其请求操作被策略允许,请求监听层会将该请求放进该层维护的请求队列中。放进请求队列中的请求包含请求的客户端信息,请求的优先级,请求所要进行的具体操作等等。请求监听层会有一个请求分发线程,从请求队列中按照一定的策略找出一个请求,将该请求送往逻辑处理层。  逻辑处理层:逻辑处理层的主要功能是维护抽象的数据模型,使得请求层不必与具体的数据库访问器接口方法做交互。逻辑处理层将请求监听层分发过来的请求转换为对特定抽象数据的操作,这就使得在这一层能尽最大可能地应用面向对象设计的好处,为以后的升级,定制提供方便。逻辑处理层由请求监听模块,逻辑处理模块和逻辑对象管理器组成。请求监听模块将请求监听层分发过来的请求转换为对特定逻辑对象的操作。逻辑处理模块是一个特定的线程,负责调用逻辑对象管理器的方法来进行逻辑多项生命周期的维护。逻辑对象管理器负责提供逻辑对象的管理接口,比如逻辑对象的创建,删除等操作。  数据库访问层:数据库访问层主要由数据库访问器接口和数据库访问器实现来组成。这一层提供具体的数据访问操作给逻辑处理层使用。数据库访问器接口和数据库访问器实现分属于不同的程序集,这样就使得数据库访问器实现能很方便的被替换,使系统能迁移到使用新的数据库系统,只要我们提供新数据库系统的数据库访问器实现,并且更改数据库访问层的程序配置文件指向新的数据库访问器实现。 系统具体设计 本章节按照“系统总体架构介绍”里面描述的模块顺序逐个介绍这些模块的具体实现策略。 请求监听层: 请求监听模块: 设计请求监听模块涉及到设计我们所使用的具体的网络协议的格式,在初步的设计里面,我们使用 .Net对于 WinSocket的封装来实现网络数据的传输,使用TCP/IP协议。在TCP的payload里面传输我们的请求消息和响应消息。消息格式如下: 其中各个字段的含义如下: Command Value: 用于指示操作码,如果系统最高位是0,表明这是系统定义的操作码;如果最高位是1,则表明是用户定义的操作码。对于每一个特定的操作码,系统应该提供一个该操作码下的Request Data的解析函数;所以,如果用户需要定义自己的操作码,那么他也应该提供该操作码下的Request Data的解析函数实现。解析函数有特定的格式如下: public Dictionary RequestDataParser(ushort commandValue, ushort flags, Guid sessionId, byte[] requestData); 其输入为一个完整的包结构,输出为解析出来的Request Data中包含的有意义的字段对应C#中的数据类型,以及这些数据的值。这样子就是得我们能在我们的请求监听层初始化的时候根据配置文件中的解析函数配置注册合适的解析函数,在运行中能够调用注册好的配置函数解析对应的网络数据将其转换为有意义的逻辑数据。 在这种设计之下,用户如果需要定义自己的Command Value,假设其值为0xF000,那么用户提供一个叫做CustomeRequestDataParser.dll的程序集,其中包含了对于Command Value0xF0000下的Request Data的解析函数实现,其实现如下: public class CustomeRequestDataParser { public Dictionary RequestDataParser(ushort commandValue, ushort flags, Guid sessionId, byte[] requestData) { Dictionary result = new Dictionary(); if (requestData.Length != 8) { throw new ArgumentException("Invalid network data received."); } byte[] field1 = new byte[4]; byte[] field2 = new byte[4]; Array.Copy(requestData, field1, 4); Array.Copy(requestData, field2, 4, 4); result.Add( typeof(uint), BitConverter.ToUInt32(field1, 0)); result.Add(typeof(uint), BitConverter.ToUInt32(field2, 0)); return result; } } 在我们的请求监听层的程序配置文件中存在这么一项: 在我们的请求监听层的初始化代码中存在这么几行代码: public void RegisterRequestDataParsers() { System.Collections.Specialized.NameObjectCollectionBase.KeysCollection keysCOllection = ConfigurationSettings.AppSettings.Keys; List requestDataParserKeys = new List(); foreach (string keyValue in keysCOllection) { if (keyValue.Contains("CommandValueParser")) { requestDataParserKeys.Add(keyValue); } } foreach (string keyValue in requestDataParserKeys) { string assemblyPath = GetAssemblyPath(ConfigurationSettings.AppSettings[keyValue]); if (!IsAssemblyLoaded(assemblyPath)) { AppDomain.CurrentDomain.Load(assemblyPath); } string typeName = GetTypeName(ConfigurationSettings.AppSettings[keyValue]); ushort commandValue=GetCommandValue(ConfigurationSettings.AppSettings[keyValue]); if (!registeredRequestDataParsers.ContainsKey(commandValue)) { registeredRequestDataParsers.Add(commandValue, Type.GetType(GetTypeFullName(typeName))); } } } 当请求监听模块收到一个请求之后,它会将其转换为一个事件,将其放进事件队列中,一个事件应该包含以下信息: public class Event { private DateTime timeStamp; private ushort commandValue; private ushort flags; private Guid sessionId; Dictionary requestData; } 一个时间队列的可能定义如下: public class EventQueue { private List eventPool; private uint eventCount; private Event mostPrioritizedEvent; } 请求调度模块不断的查询事件队列中的时间,按照特定的调度策略找出一个合适的事件交给逻辑层处理: public abstract class EventSelectionPolicy { } public class EventQueueProcessor { private EventQueue eventQueue; private EventSelectionPolicy eventSelectionPolicy; public void RegisterEventQueue(EventQueue queue) { if (this.eventQueue != null) { throw new ArgumentException("Unable to register another event queue when a queue is still in processing."); } this.eventQueue = queue; } public EventQueue UnregisterEventQueue() { if (this.eventQueue == null) { throw new InvalidOperationException("Unable to unregister an event queue when the queue is not intialized."); } EventQueue queue = this.eventQueue; this.eventQueue = null; return queue; } public void RegisterEventSelectionPolicy(EventSelectionPolicy policy) { this.eventSelectionPolicy = policy; } public Event SelectEvent() { // //return ...; } } EventQueueProcessor调用SelectEvent方法按照注册过的调度策略选择一个event交给逻辑处理层处理。 后记

posted on 2009-11-06 13:26 悟山 阅读(202) 评论(0)  编辑 收藏 引用


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理