动态配置型软件架构-----客户信息管理系统软件架构分析设计

动态配置型软件架构

------客户信息管理系统软件架构分析设计

1.      概述

客户信息管理系统是基于客户(王总)的超前的设计思想和简化二次开发客观需求,因此客户系统必须具有高度的可扩展性和适应性,并且是具有可配置的柔性系统,包括可定制的菜单,可定制的列表,可定制的表单;同时自动生成表单JS校验,自动生成CRUD(增,删,改查)SQL语句和变更更正审核SQL语句,并自动完成数据库操作。为了方便二次开发,适应新的业务需求和校验需求,我们还提供了柔性接口供实现。

2.      客户信息软件架构设计

客户信息管理系统设计分为数据库设计、软件概要设计及详细设计,其中数据库设计主要集中在模版组、模版信息的存储设计,动态菜单、数据字典的存储设计。软件设计包括模版及模版组信息的CRUD操作,基于模版信息的动态列表,动态表单,动态JS函数,定制JS函数,动态SQL,动态缺省数据,业务校验接口设计。

2.1.    显示模版设计

2.1.1. 模版数据库设计图(pdm):

2.1.2. 模版表间关系说明

从上图可以看出,模版组(TempletGroup)和模版组用户(TempletGroupUser)理论上是一对一关系,从而每个能管理客户信息的用户就拥有一个模版组,每个模版组包含若干个模版(Templet),每个模版对应与一个客户信息的业务表(TempletTable),每个模版对应多个模版字段(TempletField),模版字段中包括表单元素的显示属性,包括是否显示,是否必填,是否跨列显示,显示表单元素的类型(输入框,下拉框等),列表显示属性(显示的列名),及业务上的修改或变更是否需要审核等。因为客户关系管理系统的业务表对应多个模版,因此肯定有共有的属性,同样字段也有共有的属性,所以设计了TempletTableTempletTableColumn两个表,同时这个TempletTable表还是定制菜单的基础数据。在理论上,一个用户拥有一个模版组,一个模版组拥有n个用户。

2.1.3. 模版表的详细设计

2.1.3.1.   Templet的基本信息

名称

Templet

注释

模版组模版项目明细

 

 

2.1.3.2.   Templet

名称

注释

数据类型

文本说明

强制

templetId

模版项目ID

INTEGER

 

TRUE

templetTableId

模版表ID

INTEGER

对应templetTable

FALSE

templetGroupId

模版组ID

INTEGER

 

FALSE

templetCategoryId

模版类型ID

INTEGER

不使用

FALSE

templetDisplayName

模版显示名称

VARCHAR2(100)

 

FALSE

templetListJspFile

列表显示模版文件(暂不使用)

VARCHAR2(200)

不使用

FALSE

templetJspFile

对应的JSP文件名称和路径(暂不使用)

VARCHAR2(200)

不使用

FALSE

templetPageName

对应的页面名称(暂不使用)

VARCHAR2(200)

不使用

FALSE

templetDelegateClass

模版对应的代理类(暂不使用)

VARCHAR2(200)

不使用

FALSE

templetRanking

显示顺序

INTEGER

 

FALSE

templetExtend

模版扩展信息

VARCHAR2(200)

未使用

FALSE

templetTablePeriod

报表周期,,季度,,,

VARCHAR2(10)

未使用

FALSE

templetTablePromptPeriod

报表提示周期,,季度,,,

VARCHAR2(10)

未使用

FALSE

templetState

状态,1可用,0不可用

CHAR(1)

 

FALSE

 

2.1.3.3.   TempletField的基本信息

名称

TempletField

注释

模版表字段明细

 

2.1.3.4.   TempletField

名称

注释

数据类型

文本说明

强制

fieldId

字段id

INTEGER

 

TRUE

templetTableColumnId

列流水号

INTEGER

对应templetTableColumn

FALSE

templetId

模版项目ID

INTEGER

 

FALSE

columnName

数据库表列名(暂不使用)

VARCHAR2(100)

不使用

FALSE

fieldName

列名称(暂不使用)

VARCHAR2(100)

不使用

FALSE

fieldDisplayName

显示名称

CHAR(100)

 

FALSE

formTypyId

表单元素类型ID

INTEGER

 

FALSE

formSelectId

下拉表单对应的编码表ID

INTEGER

 

FALSE

formDefaultValue

表单元素默认值

VARCHAR2(200)

 

FALSE

formVerifyRuleId

表单元素校验规则ID

INTEGER

 

FALSE

formMaxlength

表单输入数据长度

INTEGER

 

FALSE

formWidth

表单元素宽度

INTEGER

 

FALSE

formHeight

表单元素高度

INTEGER

 

FALSE

formIsSpan

表单是否跨列显示,1,0

CHAR(1)

 

FALSE

isRequired

是否为必填项,1必填,0可不填

CHAR(1)

 

FALSE

isDisplay

是否可以显示,1可以,0不可

CHAR(1)

 

FALSE

isModify

是否可编辑,1 可以,0 不可以

CHAR(1)

 

FALSE

isAlterCheck

是否要变更复核,1,0

CHAR(1)

 

FALSE

isModifyCheck

是否需要修改复核

CHAR(1)

 

FALSE

isListDisplay

是否在列表中显示,1是,0

CHAR(1)

 

FALSE

isUserDefined

是否允许用户自定义编辑,1 0

CHAR(1)

 

FALSE

fieldOrder

是否对字段排序,默认为空,填写desc asc

VARCHAR2(10)

 

FALSE

fieldExtend

表单后缀信息

VARCHAR2(200)

 

FALSE

fieldRanking

显示顺序

INTEGER

 

FALSE

fieldAlign

显示对齐方式,left 居左 ,center 居中 ,right 居右

VARCHAR2(20)

 

FALSE

fieldDisplayStyle

字段显示样式

VARCHAR2(20)

 

FALSE

2.1.3.5.   TempletTable的基本信息

名称

TempletTable

注释

业务树菜单项目表

 

2.1.3.6.   TempletTable

名称

注释

数据类型

文本说明

强制

templetTableId

模版表ID

INTEGER

 

TRUE

templetTableBaseType

类型-法人表,自然人表

VARCHAR2(100)

 

TRUE

templetTableType

表类型,0主表,1普通,2财务报表,3外部表,9公共基本表。如果该属性为空,表示该项是个节点,不是具体的业务

CHAR(1)

 

FALSE

templetTableParentId

父表Id,如果是跟为0

INTEGER

 

TRUE

templetTableName

物理表名称,如果是一级,二级节点为空

VARCHAR2(100)

 

FALSE

templetTableKey

关键字,外部系统调用时指定该Key

VARCHAR2(100)

暂时没有使用

FALSE

templetTableDisplay

表显示名称

VARCHAR2(100)

 

FALSE

templetTableExtend

表扩展信息

VARCHAR2(200)

记录了主表的加载类的名称

FALSE

templetTableJspForm

表对应的Form JSP文件

VARCHAR2(200)

不使用

FALSE

templetTableJspList

对应列表显示Jsp文件

VARCHAR2(200)

不使用

FALSE

templetTableJspControl

表业务对应的 控制器

VARCHAR2(200)

菜单树对应得JSP文件

FALSE

templetTableRanking

显示顺序号

INTEGER

 

FALSE

templetTableState

状态,1可用,0不可用

CHAR(1)

 

FALSE

2.1.3.7.   TempletTableColumn的基本信息

名称

TempletTableColumn

注释

数据库表物理字段,对应数据字典

 

1.1 TempletTableColumn

名称

注释

数据类型

文本说明

强制

templetTableColumnId

列流水号

INTEGER

 

TRUE

templetTableId

模版表ID

INTEGER

 

FALSE

templetTableColumnName

列名称

VARCHAR2(100)

 

TRUE

templetTableColumnComment

列说明

VARCHAR2(500)

 

FALSE

templetTableColumnType

列类型

VARCHAR2(100)

 

TRUE

templetTableColumnSize

列长度

INTEGER

 

FALSE

templetTableColumnNull

是否可以NullY是,N

CHAR(1)

 

TRUE

templetTableColumnRanking

列序号

INTEGER

 

FALSE

 

2.2.    基于模版的列表设计

2.2.1. 列表设计示意图:

如图所示,从类及接口的命名方面就可以很清楚地知道设计思想,DataMap是接口,DefaultDataMap是缺省实现类,GeneralListMap是具体实现类,ColDatamgrExtendColDataMgr是管理DataMap接口的类。listTemplet.jspjsp列表模版,模板使用ColMetaInfo来展现列表,普通的Jsp只需要include列表模板即可。

根据需求分析,列表页面是基于模版设置,显示相关表头,根据来自数据库的查询数据和模板设置,按一定的格式显示表体。因此我们要构架一个数据和显示的桥梁,显示属性是基于模版设置(封装到ColMetaInfo对象),数据是取之客户信息业务表(每行数据是放到HashMapkey是列名,value值是字段的值,把HashMap加入到ArrayList中),它们之间的桥梁就是列名。

2.2.2. ColDataMgr说明

ColDataMgr的主要功能是,获取相关的数据和显示信息。

2.2.2.1.   获取列显示信息

public ArrayList getColMetaInfos()

       {

              。。。 。。。

       }

2.2.2.2.   获取显示数据ArrayListlist列表中每个元素为HashMap

       public ArrayList getData()

       {

              。。。 。。。

       }

2.2.3. 列表模版ListTemplet.jsp实现说明

使用jsp模版可以把显示格式和显示逻辑分离,同时我们可以看出ColDataMgr其实就是处理显示的逻辑和数据,而ListTemplet.jsp处理显示的格式。推而广之,我们可以根据不同的显示需求,产生多个类似 ListTemplet.jsp 如适应打印格式的模版,适应导出Excel格式的模版,细心的读者在看源代码的过程中会发现,DataMap接口中都有类似的方法,如getExeclColMetaInfos()getPrintColMetaInfos(),在缺省实现类中都有缺省实现,如果需要,直接在子类中覆写(override)此类方法即可。

<%

  //获取后台传来的列表显示属性和显示数据

       ArrayList colMetaInfos = (ArrayList) request.getAttribute("colMetaInfos");

       ArrayList datas = (ArrayList) request.getAttribute("datas");

      。。。 。。。

%>

//根据colMetaInfos显示表头

。。。  。。。           

<TR>

//循环显示列名称

                     <TD class=ItemTitle height=20 nowrap>

                            <%=colMeta.getDisplayName()%>

                     </TD>

            

 </TR>

//通过嵌套循环,根据colMetaInfosDatas显示表体

。。。   。。。

 

 //没有记录显示空白行

。。。。。。

 </table>

2.2.4. ExtendColDataMgr说明

列表设计的范围是对单表的显示,使用ColDataMgr就可以了,为了达到多个关联表(一对一关系)显示,使用ExtendColDataMgr,ExtendColDataMgr代码如下:

public class ExtendColDataMgr

{

       。。。  。。。

//初始化类的相关属性

       public ExtendColDataMgr(ArrayList oldColMetaInfos, ArrayList oldDatas, ArrayList newColMetaInfos, HashMap newDatas, String linkKey)

       {

              。。。 。。。

       }

      

       public ArrayList getDatas()

       {

               。。。 。。。

       }    

}

从代码上可以明显地看出,ExtendColDataMgr是对ColDataMgr的进一步扩展,其中ArrayList

(oldDatas) 的每个元素都是HashMap,每个HashMap中都有linkKey,该键为外键的值(110),而在新的数据是HashMap,其中的key就是linkKey(110) value值是HashMap。从而可以把新的值逐行加到旧的值内部。下面是数据结构示意图:

                OldDatas

             newDatas

name

password

adress

linkKey

linkKey

age

sex

张三

122

长春

110

112

23

1

李四

123

北京

111

111

25

0

王五

124

广州

112

110

24

1

2.3.    基于模版的表单设计

2.3.1. 表单设计示意图:

如上图所示,FormDisplayMgr是管理接口FormMap的管理类,AbstractFormMap类是缺省实现类,GeneralFormDisplayMap是通用的表单影射类,ExtendFormMap类是FormMap接口的代理封装类,ExtendFormMap类实现了FormMap接口,又封装了FormMap。同样showInfoTemplet.jspformTemplet.jspjsp模版文件,普通的表单jsp只要include模版jsp就行了。showInfoTemplet.jspformTemplet.jsp是使用DisplayAttribute来显示页面,其中ShowInfTemplet.jsp用于显示详细信息页面,formTemplet.jsp用于新增、修改页面。

根据需求,显示页面要基于模版配置,配置的属性经过实现接口FormMap的封装,产生DisplayAtttributeArrayList,然后再在页面上显示出来。当然还有自动校验Js语句,

2.3.2. ExtendFormMap说明

ExtendFormMap同样是对GeneralDisplayMap的再次封装,主要适用于多个表数据放入一个表单中:

public ArrayList getDisplayAttributes(){

              ArrayList list = this.formMap.getDisplayAttributes();

                     list.addAll(new GeneralFormDisplayMap(colDisplayMetaDatas, selectValuesMap, colDefaultData).getDisplayAttributes());             

              return list;

}

2.3.3. FormSelectAttribute说明

在最初的设计中,并没有FormSelectAttribute类,是后来提出下拉菜单要求有级联关系,如省,市, 县之间有级联关系,原来的设计是在DisplayAttribute类中有二维数组,存储下拉菜单的valuetext。为了满足新的需求,加上了FormSelectAttribute类,加上了子下拉菜单的名称,代码如下:

public class FormSelectAttribute {

       private String selectName;

       private String subSelectName;

       private String[][] allOptions;

    。。。。。。

}

2.3.4. 级联下拉菜单JS实现说明

2.3.4.1.   级联菜单联动

下拉菜单的级联是通过父菜单发生改变,子菜单也发生变化,即JSonChange来触发:

οnchange="chageDiv_<%=formElementsKey%>(this, '<%=display.getSelectValues().getSubSelectName()%>')"

2.3.4.2.   JS实现下拉菜单重绘

下拉菜单封装在DIV中,因此改变DIV中的内容即可实现,使用JSinnnerH
TML
即可实现:

document.getElementById("div_" + targetId).innerHTML = str;

2.3.4.3.   多级(n级)菜单的联动

为了实现多级(n级)菜单的联动,使用了递归算法:

function nestFlushSubSelect(targetId)

{

    。。。。。。

 

<%

                if(selectMap.get(display.getSelectValues().getSubSelectName()) != null)

                {

             %>

                nestFlushSubSelect(<%=display.getSelectValues().getSubSelectName()%>);

             <%}%>

。。。。。。

}

2.4.    基于模版的JS校验设计

2.4.1. JsFunctionGenerator接口:

public interface JsFunctionGenerator {

       public String getFunctionText();

}

2.4.2. ValidateFunctionGenerator的构造方法介绍

2.4.2.1.   使用模版定义的单纯的JS校验

public ValidateFunctionGenerator(long templetId)

{

              。。。 。。。

}

2.4.2.2.   除了模版定义的单纯JS校验,另外定制的js校验 accessFunctions

public ValidateFunctionGenerator(long templetId, String accessFunctions)

{

       。。。 。。。

}

2.4.2.3.   适用于多个表的同一表单操作,根据functionNo可以区别不同的校验

public ValidateFunctionGenerator(long templetId, String accessFunctions, String functionNo)

{

              。。。 。。。

}

2.4.3. 表单元素间的互相校验

表单元素间的互相校验关系可以写在表单提交函数的后面。

2.5.    基于模版的SQL语句生成器设计

2.5.1. SQL语句生成器设计示意图:

2.5.2. BizSqlGenerator接口说明

其实从接口上考虑,并没什么稀奇之处,代码如下:

public interface BizSqlGenerator {

       public String getSql();

       public String[] getSqls();

}

即要么得到一个sql语句,如Select语句,要么得到Insert语句,要么得到一组语句,如审核Sql语句,还有是既要一条语句,又要一组语句,如撤销。

2.5.3. DefaultBizSqlGenerator缺省实现类说明

为此,我们设计了缺省实现类,如下:

public abstract class DefaultBizSqlGenerator implements BizSqlGenerator {

       public  String getSql()

       {

              return null;

       }

       public String[] getSqls()

       {

              return null;

       }

}

2.5.4. SqlGenerator子类实现说明

其下的子类,都各有各的构造方法,从而初始化类的相关属性。从这个方面来说,面向对象编程其实是面向接口编程,接口内只定义外界调用的方法,至于怎么实现,实现的条件等问题由实际实现类来考虑,下面以审核SQL语句生成器例子说明,其他几个子类都类似。

2.5.4.1.   审核SQL语句生成器BizAuditSqlGenerator实现说明

  //构造方法要完成初始化modifyInfos,修改的内容

private String[] ids;

    private ArrayList modifyInfos = new ArrayList();

    private String userId;

       public BizAuditSqlGenerator(String[] ids, String userId)

       {

              this.ids = ids;

              this.userId = userId;

              init();

       }   

       public String[] getSqls()

       {

              //使用modifyInfos生成审核SQL语句数组,这样就达到了既实现了接口的方法,又可以不增加新的接口方法的目的。

        。。。  。。。

       }

  //初始化modifyInfos

  private void init(){

   。。。 。。。     

  }

2.6.    动态缺省数据设计

缺省数据是由模版定义的,在新增页面上,显示的就是模版定义的缺省数据,但有些缺省数据则直接和运行时间有关,而不是死的缺省数据。

2.6.1. DefaultValueMgr 实现说明:

相当于一个工厂方法,根据defaultKey来创建类并调用方法获取缺省值。从而为下一步二次开发提供了基础。

public class DefaultValueMgr {

       public static String getValue(String defaultKey)

       {

              String ret = "";

              if(defaultKey.equals(ClientConstant.DefaultValue.CURRENT_YEAR))

              {

                     ret = (new Integer(DefaultDate.getCurrentYear())).toString();

              }

              。。。 。。。

              return ret;

       }

 

}

2.6.2. DefaultDate 实现说明:

public class DefaultDate {

   public static int getCurrentYear()

   {

          return Calendar.getInstance().get(Calendar.YEAR);

   }

   public static int getCurrentMonth()

   。。。 。。。

   public static int getCurrentDay()

   。。。 。。。

}

ClientConstant的内部类ClientConstant,在模版中也是存储这样的缺省值,当程序获知这样的缺省值时,就会产生相关的缺省值,同样如果以后有新的需求也可以修改这样三个类的代码。

2.6.3. ClientConstant中的缺省值

该值同样保存在模版的缺省值字段中,当程序读到这样的值,就会自动调用相关的方法来获取缺省值

public static class ClientConstant {

               public static final String CURRENT_YEAR = "$default_current_year";

               public static final String CURRENT_MONTH = "$default_current_month";

               public static final String CURRENT_DAY = "$default_current_day";

 }

2.7.    业务校验接口设计

通过上图可知,FiltetFactory工厂类根据模版IdtempletId),调用静态常量类ClientConstant,获取相应的实现FilterOperator接口的实现类的全名(含包名FullName)字符串,FiltetFactory工厂类使用Class.forName(类的全名),创建FilterOperator接口的实现类,调用FilterOperator接口的实现类的接口方法即可。

2.7.1. 业务过滤工厂类FilterFactory

public class FilterFactory {

       /**

        * 根据templetTableId,从ClientConstant中得到过滤类的全名(含包名),

        * 根据类名称创建过滤类

        * @param templetTableId

        * @return

        */

       public static FilterOperator getFilterOperator(String templetTableId)

       {

              。。。 。。。

                                                        filterOperator = (FilterOperator)(Class.forName(filterOperatorClassName).newInstance();

       。。。 。。。

       }

工厂类依赖静态常量类的配置,同样你可以自己写配置文件,或properties,或xml,当使用bean.xml来配置,恭喜你,你在使用Spring,事实上Spring的底层也没什么高招,也就是用java的反射而已。

2.7.2. ClientConstant的静态内部类 FilterProperty

public static class FilterProperty

       {

              private static  HashMap filterClassProperties = new HashMap();

       //初始化filterClassProperties templetId,和类FullName初始化

              private static void initFilter()

              {

                     。。。 。。。

                     filterClassProperties.put("100681", "com.iss.itreasury.clientmanage.customer.bizlogic.valid.CorpComeMarkInfo");

                     。。。 。。。

              }

         //根据模版IdtempletId),获取相应的实现FilterOperator接口的实现类的全名(含包名FullName)字符串

              public static String getFilterClassName(String key)

              {

                     initFilter();

                     return (String)filterClassProperties.get(key);

              }           

       }

2.7.3. AbstractFilterOperator 运用了命令模式

public abstract class AbstractFilterOperator implements FilterOperator {

 

       protected String msg;

 

       public boolean isPass(HashMap hm) {

 

              String act = (String) hm.get("operation");

 

              boolean bool = false;

 

              if (ClientConstant.BizWebOperation.BIZ_INSERT.equalsIgnoreCase(act)) {

                     bool = validInsert(hm);

              } else if (ClientConstant.BizWebOperation.BIZ_MODIFY

                            .equalsIgnoreCase(act)

                            || ClientConstant.BizWebOperation.BIZ_CORRECT

                                          .equalsIgnoreCase(act)) {

                     bool = validModify(hm);

              } else if (ClientConstant.BizWebOperation.BIZ_DELETE

                            .equalsIgnoreCase(act)) {

                     bool = validDelete(hm);

              } else {

                     bool = false;

                     this.msg = "无法识别的操作类型";

              }

              return bool;

       }

 

       public String getAlterString() {

              return this.msg;

       }

   。。。 。。。

}

2.8.    非模版的特殊需求设计

有些不适用于模版处理的情况,例如模版的定制、报表等模块开发,可以直接使用类似于其他模块那样,自行实现,与模版无关。

3.      设计总结

客户信息管理系统是基于模版定制的软件架构,是在数据库中定义的模版的基础之上,展示动态列表、动态表单、动态JS校验、动态SQL、自动执行的功能,完全实现了配置化管理。并且留有大量的可扩展的接口,简化和优化了二次开发,是非常有益的设计尝试和超前的设计理念。

努力,在于我热爱我的事业,与中国的软件一起走向成熟,走向世界。
   
联系作者: lijj_72@hotmail.com
 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值