AEAI DP开发平台精要

1 背景概述

相信很多了解数通畅联软件的人对AEAI DP应用开发平台并不陌生,笔者在入职第一天就开始接触AEAI DP,使用AEAI DP开发过AEAI WM、AEAI CRM以及中国XXXX管理系统项目,在此过程中对AEAI DP有了较为深入了解,工作之余尝试对AEAI DP的工作原理、实际开发工作涉及的技术点进行梳理,希望能够对其他AEAI DP初学者和使用人员有所帮助。

本文相关说明涵盖AEAI DP的远程热部署新特性,AEAI DP V3.5以后版本支持按工程、模块、资源不同粒度实现代码远程增量部署,类似AEAI ESB中的按流程、服务、工程远程热部署模式。

2 预期读者

  • 数通畅联内部技术人员

  • 外部AEAI DP使用人员

3 技能要求

  • 了解Java Web开发过程,熟悉相关知识点

  • 对AEAI DP技术手册所涉及的内容熟练掌握

4 架构及原理

4.1 DP产品架构解读

wKioL1ZhDN2x-VlGAACKZJ8t3ms992.png

AEAI DP开发平台包括三部分,第一部分是一站式的Java Web框架,在数通畅联软件家族中命名为Hotweb,第二部分基于Eclipse插件的扩展开发设计器,在数通畅联软件家族中命名为Miscdp Studio,第三部分是用于开发调试的服务器HotServer。基于Miscdp Studio可以开发普通Java Web应用、集成Java Web应用,还可以为BPM流程平台开发业务表单及流程相关功能。

Hotweb是一站式的MVCFramework,对前端页面交互及控制层做了统一封装,各类功能模型的交互方式封装在父类中。每个页面提供一个Handler处理Java类和展现JSP页面,一个功能可能涉及多个页面,复用一套业务逻辑处理Service和通用的数据存取持久工具类DAOHelper,在Service层调用通用数据存取类DAOHelper。数据持久层基于IBatis封装,Service层基于Spring来做事务控制和服务配置。

4.2 Howeb交互机制

wKiom1ZhDJKxLvTZAACmJbG_EsE844.jpg

  1. DispatchServlet:前端请求分发,配置于web.xml

  2. Handler:具体请求动作处理及控制,调用Service,返回ViewRender

  3. Service:后台业务处理,包括接口及实现,Spring配置,通过DaoHelper调用SQLMap

  4. SQLMap:负责数据持久化处理(CRUD)

  5. ViewRender:前端展现渲染方式定义,实例化PageBean,放置于request的attributes中

  6. PageBean:承载前端展现的属性、方法

  7. JSP:具体页面代码,使用PageBean来获取handler中设置相关数据。

典型请求页面交互过程模式如下:

  • 页面提交发起,一般是post方式至DispatchServlet

Servlet根据HandlerId实例化Handler、根据actionType反射调用对应的处理方法,方法名跟actionType一致

  • Handler处理

在对应方法中,调用Spring配置的Service来存/取数据库

  • 根据需要返回不同的ViewRender

如果是ajaxRender直接response返回页面,异步加载或者做相关处理

如果是dispatchRender,一般是转向其他的handler来进行处理

如果是LocalRender,直接调用prepareDisplay来做处理

4.3 典型目录层次结构

wKioL1ZhDSyBmzvYAABQlrce_t8486.png

如上图:module该目录是放置各模块的代码,每个模块有各自的Handler、Service、SQLMap以及配置文件,这种文件结构模式能够支持AEAI DP实现按模块来部署。

common包里放置的是公共类,controller包里方式放置的框架级handler,cxmodule里面方式的公共service接口和实现类。

4.4 核心配置文件说明

通过AEAI DP平台开发生成相关的配置,下面对核心配置文件进行说明:

wKioL1ZhDUvCbkB_AAAnUBCAdbs719.pngwKiom1ZhDOKBl8fDAAAVl3pUjTA117.png

4.4.1 web.xml配置

在web.xml中包含filter的定义以及实现

wKiom1ZhDQDhcvSyAAAU2KpFzRo384.png

wKioL1ZhDWqQSwQIAAAJew5yZUc478.png

Servlet的定义以及实现

wKiom1ZhDQrg46r9AAA7njYzqMI203.png

       以及Listener

wKiom1ZhDRugQ7McAAAMpHDFkl0662.png

4.4.2 Handler配置

1.  HandlerIndex.xml:定义了HandlerId所属的模块,通过Id对应的模块查找对应模块下的映射关系

wKiom1ZhDXbgy4sNAABtW1GwqCE613.png

2.  HandlerModule.xml与HandlerContext.xml中均映射关系指向对应的Handler以及JSP。其中HandlerModule.xml中定义的是不同模块下的映射关系而HandlerContext.xml中是公共访问的如主页、左侧功能列表的显示等。

wKioL1ZhDfTATPEHAAAoK_qc7WM913.png

wKiom1ZhDYvBLRq0AACeDMzxZW4988.png

4.4.3 Service 配置

1.  ServiceContext.xml中典型配置属性:如数据库、appConfig等;

2.  ServiceIndex.xml:通过seviceId所对应的模块去对应模块下的ServiceModule.xml配置文件中查找对应的ServiceImpl以及SqlMap等信息wKiom1ZhDbnjV-B4AAB0uO6hxdA531.png

3.   ServiceModule.xml将对应功能模块下的Service层Controller层,用到的java类封装成bean,并通过property指定对应的SqlMap。

wKioL1ZhDj2zzbFXAABMR0y1vRs697.png

4.   ServiceContext.xml中,配置了公共调用的Service的实现、配置数据库连接、以及事务控制机制。

wKioL1ZhDmaDsSoNAAC_VrSbOdE955.png

wKioL1ZhDmaTyQ3AAAClIZGr1i8739.png

4.4.4 DB 相关配置

1.  hotweb.properties:配置了数据库的连接信息,如用户名、密码等。

wKioL1ZhDomjOUP4AAAYvCe8pa4033.png

2.  SqlMapModule.xml与SqlMapConfig.xml:是sqlmap的索引文件

wKiom1ZhDiyzCmtsAACECcdbE6M924.png

3.   sqlmap:在对应的xml中定义SQl进行数据库操作,sqlMap文件的namespace默认是由表名来生成,在ServcieContext.xml(ServiceModule.xml)中指向业务处理ServiceImpl中

wKioL1ZhDrHTUM_TAAAa43pUe34659.png

wKioL1ZhDrHSDPqEAABXiz7NcFo221.png

5 开发与部署

5.1 工具设置

wKioL1ZhDuazv5jmAAII0xR1gjw389.png

5.2 开发步骤

AEAI DP开发平台基于数据库驱动开发,推崇适度需求调研、数据库设计先行、原型驱动的敏捷开发模式。整体过程如下:

1.     通过工程向导创建可运行调试的工程框架,默认预置系统管理功能

2.     设计器向导选择对应的功能模型,来创建功能实例

3.     功能实例配置信息首先通过数据表或SQL来默认生成

4.     在设计器进行特定调整(如:查询条件、显示字段、表单元素、校验控制等)

5.     由代码生成器,生成相关的代码及配置

6.     直接部署运行,查看效果,根据实际需要调整设置,重复4—6步

7.     根据实际需要扩展功能代码、调试。

注意:一旦扩展了代码,就不能再次代码生成,那样会覆盖扩展的代码。

5.2.1 开发约束说明

  1. 所有的表都需要创建UUID逻辑主键(36位)

  2. 所有编码类数据在编码管理模块中统一维护

  3. 如果列表SQL有参数需要解析,必须有“where 1=1”

  4. 建议开发采用SVN或者CVS进行版本管理

  5. 如果选择上载资源,则资源关联表,只能三个字段,除了逻辑主键,另外两个字段必须为“BIZ_ID”、“RES_ID”,字段类型都为char(36),建议主键命名为“REF_ID”

6.2.2 跨模块调用说明

在AEAI DP中各个功能模块是彼此隔离的,这里就可能存在跨模块调用问题:

  1. 跨模块调用handler

不需要做特殊处理,仍然index?SomeHandlerID方式调用跨模块的handler。

  1. 跨模块调用service

把对应的接口提升到cxmodule包里,可以采用代码重构的方式,选中对应接口类,然后ctrl+shift+v。

  1. 跨模块调用sqlmap

把sqlmap索引配置迁移公共索引配置(即SqlMapModule.xml中对应配置迁移至SqlMapConfig.xml),模块中的sqlmap文件也对应迁移公共sqlmap目录。

5.2.3 常用Java类清单

类别

常用类

功能说明

编码定义及界面元素

FormSelectFactory

基于编码定义的表单下拉框Select工厂类

FormSelect

表单下拉框Select泛化模型

RadioGroup

RadioButton模型,一般基于FormSelect转换生成

CheckBoxGroup

CheckBox模型,一般基于FormSelect转换生成

树构建器及模型

TreeBuilder

树形模型构建类

TreeModel

树形泛化模型,可以生成前端的多选、单选框

权限相关

Profile

身份权限概要模型,在Handler中可以直接获取

User

用户模型,可以通过Profile来获取

Role

角色模型,可以通过User来获取

Group

分组模型,可以通过User来获取

Resource

资源模型,可以通过User来获取

数据库存取相关

DaoHelper

数据访问对象工具类,封装ibatis的相关操作

DBHelper

数据操作工具类,基于Spring的数据源配置

KeyGenerator

主键生成类,一般情况下是UUID格式

工具类相关

DateUtil

日期处理工具类

StringUtil

字符串处理工具类

FileUtil

文件处理工具类

IOUtil

输入输出流工具类

5.3 部署方式

双击项目名弹出如下界面,可以修改服务器的地址并修改对应的用户名,连接对应的服务器后部署应用即可。wKiom1ZhIZKTEeSBAAAwjWlJ7Bg259.png

注意:服务器用户名、密码在Hotserver中有对应的配置,如下图:

wKioL1ZhIhryK9HrAABwZq8-n08637.png

wKiom1ZhIbKyHINAAAF2J4NApTE606.png

    在Miscdp Studio对工程中任意目录、文件都可以右键,在右键菜单底部点击“Miscdp资源部署”,如下图:

wKioL1ZhIkzyymqwAAA9xoGs3v8763.png

弹出部署设置窗口,AEAI DP支持三种粒度部署模式:模块、资源、应用。应用就是整个工程,模块指的module目录下的各个package,除了module下的文件,其他都属于资源。可以支持多个模块或者多个资源一起部署,而且内置的部署机制会自动识别,是否要重新加载模块或者重启应用。

5.3.1 按资源部署

在除模块下部署jsp,xml,js,java文件等为资源部署。当部署的内容包含源码时,会默认重启应用wKiom1ZhIgHTdV5dAAAljG7OfWc834.png

部署的内容不包含源码时默认不会重启应用,且支持同时部署多个资源文件。wKioL1ZhIoOQVue1AAAm222y_ZY746.png

5.3.2 按应用部署

在项目名下右键资源部署为部署应用且默认重启应用。

wKiom1ZhIirRg7yrAAAmLC8tgeE533.png

5.3.3 按模块部署

在模块下选择xml、java文件等右键部署资源均为模块部署,默认会重新加载这个模块wKiom1ZhIkewEhWQAAAmXK6fNlU422.png

注意:在模块的jsp文件夹下右键资源部署(弹出界面如上图)也为模块部署,默认会重新加载部署的模块。但是如果选择是模块jsp文件夹下的jsp文件,则为部署资源,且不用重启应用(jsp是自动编译)。

5.3.4 部署后目录

wKioL1ZhItKQ6fePAACPR4JVGYg729.png

正常情况下Web应用的所有的classes都在WEB-INF/classes目录下,但基于AEAI DP开发的Web应用部署后有一个特殊目录modules,所有模块里的classes以及对应的配置文件都放置在这个目录里,如下图。

wKiom1ZhInngkdYXAACH-N4fON8291.png

5.4 调试配置

远程调试是复杂业务逻辑的错误定位过程中常用手法,通过调试也可以使程序员对代码脉络更清楚,是程序员必备的技能。AEAI DP 基于Eclipse内置远程机制进行代码调试,具体参见《AEAI DP开发平台技术手册》中的调试配置部分内容。

6 知识点汇总

6.1 分层解读

6.1.1 控制层

对应的Handler如下图:

wKioL1ZhIx-g8ahJAAAT13Dw16g240.png

wKiom1ZhIraQ0oBvAABTZySv3fI231.png

1.  prepareDisplay方法

在handler中默认执行prepareDisplay方法

wKiom1ZhItfwtZaLAAAe0qPNNt0464.png

mergeParam(param)方法中通过handlerId的对比确定需要append的param的信息

wKioL1ZhI1Hi1wQ3AAAw6nosKlU637.png

storeParam方法在页面间跳转时候,将param存到一个session中,跟mergetParam联合使用保证在页面间切换时候,列表页面的查询条件不丢失。

setAttributes(param)方法中做遍历将param中的值进行set将数据放入到页面中

wKiom1ZhIwDSYraFAAAbv-sLw-U491.png

返回new LocalRenderer(getPage()),在LocalRenderer中的executeRender方法中创建PageBean,所有handler中的属性信息都传入至PageBean中,通过request.setAttribute(PAGE_BEAN_KEY,pageBean)方法将handler信息放入页面所以在JSP中可以通过pageBean中封装的getHandlerURL()获得handlerURL。

wKiom1ZhIyfjL7klAAB9gEIJ7jo125.png

2.  processPageAttributes方法

processPageAttributes方法用于回调处理扩展属性(下拉框处理、默认值处理)

wKiom1ZhIzygFpnmAAAZki1fiSY347.png

3. initParameters方法

initParameters方法中可以设置查询条件的默认值

wKiom1ZhI07QXdyMAAAL4OZo_pg265.png

4. getService方法

在getService方法中返回到对应的调用的Service

wKioL1ZhI9GSNWjSAAAJx0HoFLo036.png

5. 扩展方法机制说明

方法名与actionType隐藏域变量一致,建议采用@PageActionanotation方式,如果不添加@PageAction,则方法名为do+”actionType”+Action

wKiom1ZhI3jiPGOZAAAXPnHhJvg573.png

6.1.2 显示层

顾名思义显示层就是用于前台显示的jsp页面如下图:

wKioL1ZhJAHRLzmMAAANWNE1iAA029.png

1.  Page指令

通过Page指令的contentType将页面编码定义为UTF-8

wKiom1ZhI66B_w4vAAAGoWZ9MHk908.png

pageEncoding是jsp文件本身的编码

contentType的charset是指服务器发送给客户端时的内容编码

2.  Jsp动作

通过jsp在头部useBean在页面引用的javaBean(即pageBean),其中class属性指定类的实例并将其绑定到名由id属性定义的变量上,scope为request,如下:

wKioL1ZhJCKD0GGNAAAIrkC3FZk829.png

相关jsp内置方法如下:

jsp:include:在页面被请求的时候引入一个文件。 

jsp:useBean:寻找或者实例化一个JavaBean。 

jsp:setProperty:设置JavaBean的属性。 

jsp:getProperty:输出某个JavaBean的属性。 

jsp:forward:把请求转到一个新的页面。 

jsp:plugin:根据浏览器类型为Java插件生成OBJECT或EMBED标记

3. Jsp表单

一般情况下页面均会包含一个表id为form1,且内部有一个隐藏域actionType在表单提交时需要赋值。

一般情况下明细表单除了actionType以外还会包含一个operaType隐藏域,用来区分当前操作的状态是insert还是 update。

wKiom1ZhI83DljXSAAAUO6HZXBs893.png

在form表单中action动作指令通过pageBean.getHandlerURL()获得handler指定跳转的路径,method="post"提交方式为post

wKioL1ZhJFDgH9IzAAEgiKqCujY067.png

4. 资源文件

在Jsp页面中引入通用的资源文件:

<%@include file="/jsp/inc/resource.inc.jsp"%>在该页面中包括常用的javascript及css

在script中定义页面处理的JS,也可以直接引用封装与于Util.js中常见的页面校验(非空、长度等)、请求方法(doDelete、doSubmit)等

wKioL1ZhJL-CLhArAAAWexYy9lg521.png

wKiom1ZhJFbBvJWUAAAXItcP1bQ530.png

util.js中封装了常用js方法如:

function doDelete(itemValue)

function doQuery(params)

function doRequest(actionType,params)

function doSubmit(reqOptions)

function postRequest(form,params)(ajax处理)

function sendRequest(targetUrl,params) (ajax处理)

style.css为最常用css,其中定义form、table、tr、td样式以及按钮的图标样式等

wKioL1ZhJNiz_JO2AABlvuOxYcc476.png

6.1.3 服务层

后台业务逻辑生成接口与实现类如下图:

wKioL1ZhJPqwVLu6AAARzScRpqc807.png

后台业务逻辑都生成接口与实现类,都在ServiceContext.xml(ServiceModule.xml)中配置。在实现类中定义对数据库的操作

wKioL1ZhJQmwLmYVAABuizF2vgs506.png

1. 继承关系说明

每个功能模型都有自己的父类和接口,继承关系如下图所示:

wKiom1ZhJPGwtB2FAABCDdFrypg633.pngwKiom1ZhJPGCpkD3AAAi_RzR8b0519.png



2. processDataType

根据数据表的元数据来转换处理表单传过来的数据,可以扩展设置类型转换器,类型转换器扩展设置在ServiceContext.xml或者ServiceModule.xml中配置,BaseService中对应代码如下:

       protected  void processDataType(DataParam  dataParam,String tableName){

              Table table =  getTableMetaData(tableName);

              List<Column> dateColumns =  table.getDateTypeColumns();

              int  count = dateColumns.size();

              for  (int i=0;i < count;i++){

                     Column column =  dateColumns.get(i);

                     String columnName =  column.getName();

                     Object paramValue =  dataParam.getObject(columnName);

                     if (paramValue != null && paramValue instanceof  String){

                            if (!String.valueOf(paramValue).trim().equals("") && !String.valueOf(paramValue).trim().equals("null")){

                                   Date  dateValue = DateUtil.getDateTime((String)paramValue);

                                   dataParam.put(columnName,dateValue);                              

                            }else{

                                   dataParam.remove(columnName);

                            }

                     }

              }

              if  (!this.fieldConvertMap.isEmpty()){

                     Iterator<String>  keys = this.fieldConvertMap.keySet().iterator();

                     while (keys.hasNext()){

                            String key =  keys.next();

                            Column column =  table.getColumn(key);

                            if (column != null){

                                   DataParamConvert  convert = this.fieldConvertMap.get(key);

                                   Object  value = dataParam.get(key);

                                   Object  convertValue = convert.convert(value, column);

                                   dataParam.put(key,convertValue);

                            }

                     }

              }

       }

3. processPrimaryKeys

处理主键默认生成机制,AEAI DP中默认是使用uuid来作为主键的,其实框架层面还支持自增字段主键机制。

    protectedvoid processPrimaryKeys(DataParam  param){

       KeyGenerator keyGenerator = new KeyGenerator();

       if (Constants.PKType.INCREASE.equals(pkGenPolicy)){

           param.put(primaryKey,keyGenerator.getMaxId(tableName, primaryKey,getDataSource()));

       }

       elseif(Constants.PKType.CHARGEN.equals(pkGenPolicy)){

           String genPk =  keyGenerator.genKey();

           param.put(primaryKey,genPk);

       }

    }

6.2 重点技术

6.2.1 常用方法

1. 在PageBean中封装了常见的页面中获取值的方法,如下:

public String  inputDate(String key)

public String  inputTime(String key)

public String inputValue(int  i,String key)

public String  selectValue(int i,String key,String formSelectKey)

public RadioGroup  selectRadio(String elementName)

public String  selectedValue(String elementName)

public String  selectedText(String elementName)

public String disabled(boolean  expression)

public String  readonly(String expression)

2. 根据需要返回不同的ViewRender,一般情况下为:AjaxRender、DispatchRender、RedirectRenderer、LocalRender

1)       AjaxRender:直接response返回值页面,异步加载或者做相关处理

2)       DispatchRender:一般是转向其他Handler来进行处理,页面跳转传递request和response可以支持跳转到第三方按Backspace可以返回

3)       RedirectRenderer:也是转向其他Handler但不能传递request和response

4)       LocalRender:显示 JSP页面

3. DateUtil类中预置许多关于日期处理的方法,例如:日期的格式化,日期的计算,获得当月、当年的第一天等等。

4. 主键生成

调用KeyGenerator.instance().genKey()来自动生成主键ID

5.  加密解密

通过CryptionUtil中的加密解密方法进行加密解密

  • 加密方法encryption("root", "12345678");其中root为需要加密的明文、”12345678”为加密的KEY。

  • 解密方法decryption(secretData, secretKey);

6. 在User类中预置了许多关于User的跟角色、群组关联属性等

  • 可以通过User user = (User) this.getUser()获取User对象

  • 并同user对象打点调用不同的方法获得User与角色和群组关联的属性

7. Dataparam转换

  • DataRow调用row.toDataParam();

  • List调用ListUtil.toParamList(records,srcKeys, replaceKeys)

8. StringUtil字符串辅助类

agileai_common的jar包中内置了很多辅助类,如StringUtil,DataUtil,ListUtil、DBUtil等。

9.  ListUtil类转换方法

预置许多List转换方法如listToMap()、toXML()、toDataSet()、toParamList()

10.   PopupBox.js中封装基于div+iframe的弹出框

Js中使用PopupBox方法时需要将对应的var写在方法的外面,每次调用使用的都是同一个弹出框,写在方法内部时每次调用方法都会新生成一个弹出框。

11.   内置Ajax处理方式

functionpostRequest(form,params)

functionsendRequest(targetUrl,params)

第一种是post方式,第二种是get方式。

6.2.2 授权配置

1.  功能授权

系统预置功能可以设置功能目录、功能节点授权,而且可以授权给角色、群组、人员。

注意:

  • 如果授权子节点,则父节点必然会授权。

  • 如果只授权到父节点,子节点没有被任何角色、群组、人员,则可以访问父节点下的所有功能节点。

  • 如果子节点已被其他的角色(群组、人员)授权,则必须要对这些子节点进行授权,而不能通过授权到父节点的方式来授权子节点。

2. 操作授权

  • 通过jsp引入标签按钮配置授权

  • 通过状态、角色控制权限

参见《AEAI DP按钮权限配置说明》,http://my.oschina.net/agileai/blog/533385

6.2.3 典型场景

1.  复制数据插入

1) 把原结果集查询出来,类型为List<DataRow>;

2) 使用ListUtil.toDataParam方法把结果集转换为参数集合,类型为List<DataParam>;

3)  使用DaoHelper调用sqlmap中配置的insertSQL来插入参数集合。

2.  排序控制说明

首先在JSP页面,添加按钮

    <td  onmouseout="onMout(this);

" align="center"><input id="upImgBtn"  value="&nbsp;" title="上移" type="button"  class="upImgBtn" style="margin-right:0px;" />上移</td>

   <td  onmouseout="onMout(this);

" align="center">

<input id="downImgBtn" value="&nbsp;" title="下移" type="

       调用的JS方法如下

function doMoveUp(){

    if (!isSelectedRow()){

        writeErrorMsg('请先选中一条记录!');

        return;

    }

    doSubmit({actionType:'moveUp'});

}

function doMoveDown(){

    if (!isSelectedRow()){

        writeErrorMsg('请先选中一条记录!');

        return;

    }  

    doSubmit({actionType:'moveDown'});

}

       上移下移在handler里的方法

    public ViewRenderer  doMoveUpAction(DataParam param){

        String  currentSubTableId = param.get("currentSubTableId");

        boolean isFirst =  getService().isFirstChild(currentSubTableId,param);

        if (isFirst){

            setErrorMsg(this.moveUpErrorMsg);

        }else{

            getService().changeCurrentSort(param,  true);

        }

        return  prepareDisplay(param);

    }

    public ViewRenderer  doMoveDownAction(DataParam param){

        String  currentSubTableId = param.get("currentSubTableId");

        boolean isLast =  getService().isLastChild(currentSubTableId,param);

        if (isLast){

            setErrorMsg(this.moveDownErrorMsg);

        }else{

            getService().changeCurrentSort(param,  false);

        }      

        return  prepareDisplay(param);

    }

       在service方法里调整两个字段排序顺序

    publicvoid changeCurrentSort(DataParam  param, boolean isUp) {

        String  subId = param.get("currentSubTableId");

        String statementId = sqlNameSpace+"."+"find"+StringUtil.upperFirst(subId)+"Records";

        List<DataRow>  records = this.daoHelper.queryRecords(statementId, param);

        String  tableName = subTableIdNameMapping.get(subId);

        String  idField = "当前表的主键字段名";

        String  currentId = "记录标识";

        String  sortField = "排序字段名";

       

        DataRow  curRow = null;

        String  curSort = null;

        if (isUp){

            DataRow  beforeRow = null;

            for (int i=0;i <  records.size();i++){

                DataRow  row = records.get(i);

                String  tempMenuId = row.stringValue(idField);

                if  (currentId.equals(tempMenuId)){

                    curRow  = row;

                    beforeRow  = records.get(i-1);

                    break;

                }

            }

            curSort  = curRow.stringValue(sortField);

            String  beforeSort = beforeRow.stringValue(sortField);;

            curRow.put(sortField,beforeSort);

            beforeRow.put(sortField,curSort);

            this.updateSubRecord(subId,curRow.toDataParam(true));

            this.updateSubRecord(subId,beforeRow.toDataParam(true));

        }else{

            DataRow  nextRow = null;

            for (int i=0;i <  records.size();i++){

                DataRow  row = records.get(i);

                String  tempMenuId = row.stringValue(idField);

                if  (currentId.equals(tempMenuId)){

                    curRow  = row;

                    nextRow  = records.get(i+1);

                    break;

                }

            }

            curSort  = curRow.stringValue(sortField);

            String  nextSort = nextRow.stringValue(sortField);

            curRow.put(sortField,nextSort);

            nextRow.put(sortField,curSort);

            this.updateSubRecord(subId,curRow.toDataParam(true));

            this.updateSubRecord(subId,nextRow.toDataParam(true));

        }

    }

2.  结果集转换为FormSelect

在进行开发的时候,会遇到select下拉框形式 的表单元素,这时就需要在控制层的pricessPageAttributes方法中将查询得到的结果集转换为formselect类型,如下例:

List<DataRow> records =  getService().findPcNameRecords(param);

FormSelect formSelect = new FormSelect();

formSelect.setKeyColumnName("PC_CODE");

formSelect.setValueColumnName("PC_NAME");

formSelect.putValues(records);

setAttribute("pcName",formSelect.addSelectedValue(param.get("pcName")));

首先通过查询得到list结果集records,结果集中需要存在有id(编码)和value(值)两列属性

然后new一个FormSelect类型的变量,然后给这个变量添加对应的id和value两个属性字段,再通过putValues方法把结果集中的数据放到formSelect中。在setAttribute中将param中对应的变量的值通过formSelect将这个选中的值存入到变量pcName中。

3. 基于HTML导出Word、PDF

1) 构造Html模板

2) 将模板转换为IO流

3)  调用对应的API进行格式转换

4.  批量处理结果集

首先需要前台jsp页面添加checkbox复选框,代码如下:

<ec:row styleClass="odd" oncontextmenu="selectRow(this,{ID_ID:'${row.ID_ID}',curColumnId:'${row.ID_ID}'});controlUpdateBtn('${row.ID_STATE}');refreshConextmenu()"  onclick="selectRow(this,{ID_ID:'${row.ID_ID}',curColumnId:'${row.ID_ID}'});controlUpdateBtn('${row.ID_STATE}');">

    <ec:column width="50" style="text-align:center"  property="_0" title="序号" value="${GLOBALROWCOUNT}"  />

    <ec:column width="25" style="text-align:center"  property="ID_ID" cell="checkbox" headerCell="checkbox"  onclick="$(this).parents('tr').trigger('click');"/>

</ec:row>

隐藏域:

<input type="hidden" id="ids"  

name="ids" value="<%=pageBean.inputValue("ids")%>" />

上面个代码选中了当前记录的ID,然后点击操作按钮调用对应的JS方法,将ids传到后台handler中处理:

<td   class="bartdx"   hotKey="D"  align="center" onclick="doDeleteRequest()">

<input value="&nbsp;"  title="删除" type="button"  class="delImgBtn" id="delete" />删除</td>

function doDeleteRequest(){

    var ids = "";

    var confirmsubMsg="您确认删除该数据吗?";

    $("input:[name='ID_ID'][checked]").each(function(){  

        ids  = ids+$(this).val()+",";

    });

    if (ids.length > 0){

        ids  = ids.substring(0,ids.length-1);

    }

    if(confirm(confirmsubMsg))  {

        //showSplash(deleteMsg);

        $("#ids").val(ids);

        doSubmit({actionType:'batchDelete'});  

    }

}

由于接到的参数为多条记录,并用逗号分隔的,需要在后台进行处理,具体代码如下:

     public ViewRenderer doBatchDeleteAction(DataParam param) {

      String ids = param.get("ids");

        if(!"".equals(ids)){

         String[]  idArray = ids.split(",");

            for(int i=0;i < idArray.length;i++ ){

             String  id = idArray[i];

             DataParam  idParam = new DataParam();

             idParam.put("ID_ID", id);

             getService().deleteClusterRecords(idParam);

            }

        }

         return prepareDisplay(param);

    }

5. 数值公式计算

我们在项目使用MVL的TemplateRuntime计算方式,具体代码如下:

publicstaticvoid main(String[] args) {

        String  testFormula = "@{value+5}";

       

        Map<String,  Double> vars = new HashMap<String, Double>();

        vars.put("value", 3.0);

        Number  number  = (Number) TemplateRuntime.eval(testFormula,vars);

        System.out.print(number);

}

 

输出后的结果:

wKiom1ZhJgiCUr5JAAAYMfcRKhI871.png

6. 后台控制jsp页面<td>标签合并

public ViewRenderer  prepareDisplay(DataParam param){

              initParameters(param);

              this.setAttributes(param);

              if  (!TreeAndContentManage.BASE_TAB_ID.equals(tabId)){

                     param.put("columnId",columnId);

                     List<DataRow>  rsList = getService().findContentRecords(filterTreeModel,tabId,param);

                     this.processRowSpanField(rsList);

                     this.setRsList(rsList); 

                     request.setAttribute("rsList", rsList);

              }else{

                     DataParam queryParam = new DataParam(columnIdField,columnId);

                     DataRow row =  getService().queryTreeRecord(queryParam);

                     this.setAttributes(row);

              }

              processPageAttributes(param);

              return  new LocalRenderer(getPage());

       }

 

privatevoid processRowSpanField(List<DataRow>  rsList){

        int spanCount = 0;

        DataRow  beforeRow = null;

        for (int i=0;i <  rsList.size();i++){

            DataRow  curRow = rsList.get(i);

            String  curRlName = curRow.getString("RL_NAME")!=null?curRow.getString("RL_NAME"):"";

            if (beforeRow == null){

                beforeRow  = curRow;

                spanCount++;

                beforeRow.put("rowSpan",spanCount);

                continue;

            }

           

            String  beforeRLName = beforeRow.getString("RL_NAME")!=null?beforeRow.getString("RL_NAME"):"";

            if  (beforeRLName.equals(curRlName)){

                spanCount++;

                beforeRow.put("rowSpan",spanCount);

                curRow.put("rowSpan",0);

            }

            else{

                curRow.put("rowSpan",1);

               

                beforeRow  = curRow;

                spanCount  = 0;

                spanCount++;

            }

        }

    }

在显示列表页面时,调用下面代码

<tr>

      <td style="text-align:center;" nowrap><%=(i+1)%></td>

      <%if (0 != rowSpan){%>

      <td rowspan="<%=rowSpan%>"><%=dataRow.getString("RL_NAME","")%></td>

      <%}%>

</tr>

6.3 学习技巧

6.3.1 利用system代码

使用AEAI DP创建工程的时候默认就会创建系统管理相关代码,系统管理里面涉及的模块几乎是所有管理系统都带的功能,这部分代码较多,值得推荐学习,事实上很多开发所需要的代码样例都可以在里面找到。

6.3.2 跟踪研读父类

Hotweb预置很多典型的功能模型,每个Handler和Service都继承对应功能模型的Handler和Service,而且hotweb源码都直接打入hotweb_core.jar,在开发、调试过程中可以跟踪研究父类来深入掌握相关调用机制、借鉴相关代码技巧。

6.3.3 查找类似代码

很多js代码、工具类的调用方式其实都散落在系统框架(预置的以及自动生成)的代码里,可以通过DP Studio(Eclipse)的文件查找方式来了解相关调用机制。

6.3.4 反编译参考代码

在hotweb中也引用了一些地方类库,如:ecside、ibatis、spring、mvel等等,包括AEAI Portal、AEAI BPM的一些classes、jar文件,都可以通过jd-gui来反编译学习分析代码,参考样例,如:反编译预置的portlet代码、参考开发新portlet。

7 感悟与收获

AEAI DP开发平台可以快速上手,对于最开始入职的我较容易产生成就感,但是随着后续深入使用,实际应用过程中的各种扩展开发,逐渐暴露出我自己的知识积累不足,同时也发现Miscdp Studio可以生成典型功能、典型的方法,便于调试部署,但更多代码的精华是藏在Hotweb框架里,Hotweb预留很多方法用于扩展,预置很多工具类通过组合使用可以几乎所有场景的功能,同时第三方的类库也很容易在AEAI DP中调用来满足实际项目的特定需求。

AEAI DP开发平台精要文档            下载

猜你喜欢

转载自agileai.iteye.com/blog/2261690