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

    技术2022-05-11  103

    动态配置型软件架构

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

    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中的内容即可实现,使用JSinnnerHTML即可实现:

    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  

    最新回复(0)