Core-Man System Design Document
Version 1.0
9/29/2009
Contents
簡介 4
系統(tǒng)總體架構(gòu)介紹 4
系統(tǒng)具體設(shè)計 6
后記 6
簡介
本文檔用來描述Core-Man這個用來為汽配行業(yè)做貨物、訂單、客戶關(guān)系管理的系統(tǒng)功能和設(shè)計。本文檔基于該系統(tǒng)的需求分析和前期市場調(diào)研,關(guān)于需求分析和市場調(diào)研的結(jié)果,可以查看文檔XXXX.
1. 系統(tǒng)設(shè)計目標(biāo)
為了使得系統(tǒng)能最大程度的滿足用戶的需求,盡可能的減少以后再定制維護(hù)過程中的代價,Core-Man系統(tǒng)應(yīng)該達(dá)到以下幾個目標(biāo):
? 可定制性:雖然本系統(tǒng)是為了汽配行業(yè)的客戶管理而設(shè)計的,但是本系統(tǒng)需要達(dá)到在特定的需求下經(jīng)過適當(dāng)?shù)呐渲茫〝?shù)據(jù)庫配置,配置文件更改)而達(dá)到滿足其他行業(yè)需求的目的。
? 可維護(hù)性:本系統(tǒng)的設(shè)計應(yīng)該達(dá)到可維護(hù)的目的,因?yàn)橛脩舻男枨罂赡軙兓ㄆ髽I(yè)規(guī)模擴(kuò)大,經(jīng)營業(yè)務(wù)增多)。在用戶變化需求的時候,我們的系統(tǒng)就需要升級,那么在升級的過程,至少需要滿足兩方面的可重用,一是數(shù)據(jù)上的可重用。客戶在一定的經(jīng)營過程中積累了一定數(shù)目的用戶資源和經(jīng)營經(jīng)驗(yàn)數(shù)據(jù),這部分?jǐn)?shù)據(jù)對于客戶以后繼續(xù)經(jīng)營,擴(kuò)大業(yè)務(wù)規(guī)模都是有重要作用的。在我們的系統(tǒng)升級的過程,應(yīng)該使得客戶可以重用這部分?jǐn)?shù)據(jù),即使得升級后的系統(tǒng)能夠識別維護(hù)舊的數(shù)據(jù)。另一方面的可重用性是指代碼上的可重用性,系統(tǒng)的升級不能使推到系統(tǒng)重來。新系統(tǒng)應(yīng)該基于舊系統(tǒng)已有的代碼,這樣可以最大程度上的節(jié)省成本。
? 安全性:雖然在系統(tǒng)初期,客戶對于安全性上的要求不會很高,但是隨著企業(yè)業(yè)務(wù)規(guī)模的擴(kuò)大,必然涉及到很多的商業(yè)保密信息。企業(yè)的客戶數(shù)據(jù),訂單數(shù)據(jù),管理模式都有很重要的商業(yè)價值,此時對于我們系統(tǒng)的安全性就提出了要求。Core-Man系統(tǒng)的安全性主要體現(xiàn)在一下幾個方面:
? 用戶認(rèn)證:系統(tǒng)應(yīng)該區(qū)分登錄的會話中用戶的身份,以此來判斷用戶可訪問哪些數(shù)據(jù),可更改哪些數(shù)據(jù),這個對于一個中大型企業(yè)里面,各個管理層面(老板,總經(jīng)理,經(jīng)理,員工)應(yīng)用同一個系統(tǒng)式很重要的。
? 權(quán)限管理:驗(yàn)證用戶后,需要判斷已驗(yàn)證用戶是否被允許訪問其所請求的數(shù)據(jù),在判斷通過的情況下,系統(tǒng)返回正確的響應(yīng);否則,系統(tǒng)告知客戶端無權(quán)訪問。
? 數(shù)據(jù)加密:企業(yè)的客戶數(shù)據(jù),員工數(shù)據(jù),訂單數(shù)據(jù)都涉及到商業(yè)秘密。這些數(shù)據(jù)都需要經(jīng)過加密后存儲在系統(tǒng)的文件系統(tǒng)或者數(shù)據(jù)庫中。
系統(tǒng)總體架構(gòu)介紹
很明顯的Core-Man系統(tǒng)是一個以數(shù)據(jù)庫為中心,以多終端進(jìn)行數(shù)據(jù)處理維護(hù)的分布式系統(tǒng),一個Core-Man系統(tǒng)的典型的應(yīng)用場景可以用下圖來描述:
在上面的應(yīng)用場景中,各個角色的分工如下:
? 中心數(shù)據(jù)庫:負(fù)責(zé)提供整個系統(tǒng)的數(shù)據(jù)的存貯,這里包含兩部分內(nèi)容
? 由數(shù)據(jù)庫管理系統(tǒng)托管的數(shù)據(jù)庫表,視圖,存儲過程,數(shù)據(jù)等信息。
? 由文件系統(tǒng)托管的圖片,文件等信息。
? 請求監(jiān)聽/代理服務(wù)器:負(fù)責(zé)作為終端用戶和數(shù)據(jù)庫之間通信的一個代理人的角色。所有客戶端的操作如果涉及到數(shù)據(jù)庫查詢/更改請求,由請求監(jiān)聽/代理服務(wù)器將其轉(zhuǎn)換為對相應(yīng)數(shù)據(jù)庫項目的查詢/更新操作。這樣的設(shè)計能使得終端用戶和數(shù)據(jù)庫的耦合性降到最低,使得在需要更換數(shù)據(jù)庫的時候,我們只需要更新請求監(jiān)聽/代理服務(wù)器里面與數(shù)據(jù)庫交互的那部分代碼。同時,加進(jìn)一層請求監(jiān)聽/代理服務(wù)器,可以讓我們的系統(tǒng)在這一層進(jìn)行安全性上的檢查更改等操作,比如,在這一層做終端用戶身份的鑒別,只有終端用戶是授權(quán)用戶的時候,我們才允許客戶端進(jìn)行后續(xù)操作。
? 超級管理終端:這個終端有最大的管理權(quán)限,用于進(jìn)行數(shù)據(jù)的備份,緊急模式下的更改等操作。
? 分布式終端1:本系統(tǒng)眾多分布式終端的其中一個,用戶為普通用戶(經(jīng)理,員工,客戶)提供訪問我們系統(tǒng)的功能。每一個分布式終端在發(fā)起連接請求監(jiān)聽/代理服務(wù)器的時候,都需要提供用戶身份的信息,請求監(jiān)聽/代理服務(wù)器用這些信息來判斷發(fā)起連接的用戶是否是一個經(jīng)過授權(quán)的用戶。
下圖描述了我們的數(shù)據(jù)庫設(shè)計的基本輪廓:
下圖描述了我們的請求監(jiān)聽/代理服務(wù)器的設(shè)計模塊以及模塊之間的聯(lián)系:
自上至下,三個大的模塊分別是:
? 請求監(jiān)聽層:負(fù)責(zé)監(jiān)聽客戶端的操作請求,檢查客戶端的身份,以及客戶端的權(quán)限。如果客戶端是經(jīng)授權(quán)的用戶并且其請求操作被策略允許,請求監(jiān)聽層會將該請求放進(jìn)該層維護(hù)的請求隊列中。放進(jìn)請求隊列中的請求包含請求的客戶端信息,請求的優(yōu)先級,請求所要進(jìn)行的具體操作等等。請求監(jiān)聽層會有一個請求分發(fā)線程,從請求隊列中按照一定的策略找出一個請求,將該請求送往邏輯處理層。
? 邏輯處理層:邏輯處理層的主要功能是維護(hù)抽象的數(shù)據(jù)模型,使得請求層不必與具體的數(shù)據(jù)庫訪問器接口方法做交互。邏輯處理層將請求監(jiān)聽層分發(fā)過來的請求轉(zhuǎn)換為對特定抽象數(shù)據(jù)的操作,這就使得在這一層能盡最大可能地應(yīng)用面向?qū)ο笤O(shè)計的好處,為以后的升級,定制提供方便。邏輯處理層由請求監(jiān)聽模塊,邏輯處理模塊和邏輯對象管理器組成。請求監(jiān)聽模塊將請求監(jiān)聽層分發(fā)過來的請求轉(zhuǎn)換為對特定邏輯對象的操作。邏輯處理模塊是一個特定的線程,負(fù)責(zé)調(diào)用邏輯對象管理器的方法來進(jìn)行邏輯多項生命周期的維護(hù)。邏輯對象管理器負(fù)責(zé)提供邏輯對象的管理接口,比如邏輯對象的創(chuàng)建,刪除等操作。
? 數(shù)據(jù)庫訪問層:數(shù)據(jù)庫訪問層主要由數(shù)據(jù)庫訪問器接口和數(shù)據(jù)庫訪問器實(shí)現(xiàn)來組成。這一層提供具體的數(shù)據(jù)訪問操作給邏輯處理層使用。數(shù)據(jù)庫訪問器接口和數(shù)據(jù)庫訪問器實(shí)現(xiàn)分屬于不同的程序集,這樣就使得數(shù)據(jù)庫訪問器實(shí)現(xiàn)能很方便的被替換,使系統(tǒng)能遷移到使用新的數(shù)據(jù)庫系統(tǒng),只要我們提供新數(shù)據(jù)庫系統(tǒng)的數(shù)據(jù)庫訪問器實(shí)現(xiàn),并且更改數(shù)據(jù)庫訪問層的程序配置文件指向新的數(shù)據(jù)庫訪問器實(shí)現(xiàn)。
系統(tǒng)具體設(shè)計
本章節(jié)按照“系統(tǒng)總體架構(gòu)介紹”里面描述的模塊順序逐個介紹這些模塊的具體實(shí)現(xiàn)策略。
請求監(jiān)聽層:
請求監(jiān)聽模塊:
設(shè)計請求監(jiān)聽模塊涉及到設(shè)計我們所使用的具體的網(wǎng)絡(luò)協(xié)議的格式,在初步的設(shè)計里面,我們使用 .Net對于 WinSocket的封裝來實(shí)現(xiàn)網(wǎng)絡(luò)數(shù)據(jù)的傳輸,使用TCP/IP協(xié)議。在TCP的payload里面?zhèn)鬏斘覀兊恼埱笙⒑晚憫?yīng)消息。消息格式如下:
其中各個字段的含義如下:
Command Value: 用于指示操作碼,如果系統(tǒng)最高位是0,表明這是系統(tǒng)定義的操作碼;如果最高位是1,則表明是用戶定義的操作碼。對于每一個特定的操作碼,系統(tǒng)應(yīng)該提供一個該操作碼下的Request Data的解析函數(shù);所以,如果用戶需要定義自己的操作碼,那么他也應(yīng)該提供該操作碼下的Request Data的解析函數(shù)實(shí)現(xiàn)。解析函數(shù)有特定的格式如下:
public Dictionary
RequestDataParser(ushort commandValue, ushort flags, Guid sessionId, byte[] requestData);
其輸入為一個完整的包結(jié)構(gòu),輸出為解析出來的Request Data中包含的有意義的字段對應(yīng)C#中的數(shù)據(jù)類型,以及這些數(shù)據(jù)的值。這樣子就是得我們能在我們的請求監(jiān)聽層初始化的時候根據(jù)配置文件中的解析函數(shù)配置注冊合適的解析函數(shù),在運(yùn)行中能夠調(diào)用注冊好的配置函數(shù)解析對應(yīng)的網(wǎng)絡(luò)數(shù)據(jù)將其轉(zhuǎn)換為有意義的邏輯數(shù)據(jù)。
在這種設(shè)計之下,用戶如果需要定義自己的Command Value,假設(shè)其值為0xF000,那么用戶提供一個叫做CustomeRequestDataParser.dll的程序集,其中包含了對于Command Value0xF0000下的Request Data的解析函數(shù)實(shí)現(xiàn),其實(shí)現(xiàn)如下:
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;
}
}
在我們的請求監(jiān)聽層的程序配置文件中存在這么一項:
在我們的請求監(jiān)聽層的初始化代碼中存在這么幾行代碼:
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)));
}
}
}
當(dāng)請求監(jiān)聽模塊收到一個請求之后,它會將其轉(zhuǎn)換為一個事件,將其放進(jìn)事件隊列中,一個事件應(yīng)該包含以下信息:
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;
}
請求調(diào)度模塊不斷的查詢事件隊列中的時間,按照特定的調(diào)度策略找出一個合適的事件交給邏輯層處理:
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調(diào)用SelectEvent方法按照注冊過的調(diào)度策略選擇一個event交給邏輯處理層處理。
后記