侧边栏壁纸
博主头像
健健康康,好好生活博主等级

行动起来,活在当下

  • 累计撰写 17 篇文章
  • 累计创建 20 个标签
  • 累计收到 3 条评论

目 录CONTENT

文章目录
OA

DEE开发说明文档

朱顺平
2022-04-07 / 0 评论 / 0 点赞 / 129 阅读 / 94301 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2024-03-08,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

DEE开发说明文档

一、DEE原理

DEE是一款数据交换引擎。

Dee可视化配置工具已经达成了基于可视化的数据交换配置、实现零代码构建多个同构、异构系统之间的数据交互。为用户构建系统之间的通信提供了简单、方便的操作模式。

dee核心概念

dee架构

一个任务分为若干个有序的适配器,在适配器1~N中对document对象(内置上下文参数)进行操作,最终将来源数据转换成输出数据。

DEE不仅可用于A8和第三方进行数据处理,还可以用于任意两套系统间的数据交换,单独部署作为数据交换服务,本文档仅介绍A8与DEE之间的结合,其余情况可参考去实现。

二、DEE数据结构

2.1 数据结构说明

DEE是基于xml数据格式进行数据传输,所有的适配器输入输出均为xml,核心对象为Document。

数据结构示例如下:

<root>
    <tableName1  count="1"  toalCount="10">
        <row>
            <fieldName1>#####</fieldName1>
            ......
            <fieldNameN>#####</fieldNameN>
        </row>
        ......
    </tableName1>
    ......

    <tableNameN  count="1"  toalCount="10">
        <row>
            <fieldName1>#####</fieldName1>
            ......
            <fieldNameN>#####</fieldNameN>
        </row>
        ......
    </tableNameN>
</root>

2.2 脚本中常用的操作方法

大家可以在dee脚本左侧查看。

// 通过xml生成docuemnt  注意这里的xml一定要按照 2.1的数据结构 可以参考 7.2.1.2 解析rest数据
document = createDocByXml(xml);

// 获取webservice结果
def wsres = getWsResult(document);

// 创建一个新的document对象,然后使用节点操作添加element
document = buildDoc();

/**
获取document根节点
参数:document对象
返回值:Element
*/
def root = getRootElement(document);

/**
快速生成element节点
参数:document对象,节点名字name
返回值:Element
*/
Element ele = buildElement(document, name);

/**
获取element的所有子节点
参数:element对象
返回值:element子节点集合 List
*/
java.util.List<Element> childs = getAllChildOfElement(element);

/**
根据名称获取element的子节点
参数:element对象,子节点名称name
返回值:子节点对象 element
*/
getChildByName(element, name);

/**
向element中设置值
参数:element对象,值value
返回值:无
*/
setVal2Element(element, value);

/**
获取element的值
参数:element对象
返回值:Object
*/
def value = getElementValue(element);

/**
删除element节点
参数:当前document对象,要删除的节点名称name
返回值:document对象
*/
document = delElement(document, name);

/**
替换element节点
参数:当前document对象,被替换的节点名称name,替换节点element
返回值:document对象
*/
document = replaceElement(document, name, element);

/**
往Element对象中插入子节点
参数:父节点elementParent,子节点elementChild
返回值:无
*/
addElement(elementParent, elementChild);

/**
合并document
参数:document对象1,document对象2
返回值:合并后的document对象
*/
mergeDoc(document1,document2);

/**
根据名称获取参数值
参数:存放参数的document对象,参数名称name
返回值:参数值 Object
*/
def param = getParaByName(document,name);

/**
向document中添加参数
参数:当前document对象,参数名称name,参数值value
返回值:无
*/
setParam(document, name, value);

/**
根据某个字段的值获取该字段所在行数据的集合
参数:当前document对象,字段名称name,字段值value
返回值:element集合 list
*/
getRowListByVal(document, name, val);

/**
获取FormData数据
参数:无
返回值:document对象
此函数会将表单数据都封装到document中
*/
document = formDataToDoc();

/**
将table转换成List<Map>
参数:document对象,表名tableName
返回值:List<Map>对象
List为行row,Map<key:字段名,value:字段值>
*/
def list = docToListMapByTable(document,tableName);

/**
转换document为Map<table,List<Map>>
参数:document对象
返回值:Map<table,List<Map>>对象
外层Map key:表名,value: List<Map<key:字段名,value:字段值>> 对象
*/
def map = docToListMapByAll(document);

/**
根据XPath获取字段值,必须要指定到具体字段,如:path=/root/tableName/row[行号]/fieldName,行号从1开始(1,2,3……),注意path区分大小写
参数:document对象, path(XPath表达式)
返回值:String字段值
*/
getValByXPath(document,path);

/**
根据XPath获取某列字段值,必须要指定到具体字段,如:path=/root/tableName/row/fieldName,注意path区分大小写
参数:document对象, path(XPath表达式)
返回值:List<String> 某列字段值
*/
getListByXPath(document,path);

/**
根据XPath设置一个节点值,必须要指定到具体字段,如:path=/root/tableName/row[行号]/fieldName,行号从1开始(1,2,3……),注意path区分大小写
参数:document对象, path(XPath表达式),val要设置的值
返回值:无
*/
setValByXPath(document,path,val);

2.2.1 Document对象操作方法

一级元素为Document根元素,只有唯一一个,名称为“root”
二级元素为“root”的子元素,定义为表名称,名称自定义但不允许重复,可以有多个,例如有dept、有member两张表
三级元素为表元素的子元素,定义为表中行记录,名称固定为“row”,可以有多个
四级元素为“row”的子元素,定义为字段,名称自定义但不允许重复,可以有多个,字段的值为数据值

格式
1.document文档,必须以root作为文档的根节点名称。
2.document如果用来存储表数据,那么必须使用document规范的表数据格式,这样DEE的适配器进行表数据操作时,才能进行识别。

  • 方法列表
    方法签名:
    Element getRootElement()
    描述:
    获取Document对象的根元素
    返回值:
    使用案例:
    Element root = document.getRootElement();//获取document根元素
    方法签名:
    Element createElement(String name)
    描述
    通过指定名称创建Element对象。
    传入参数
    返回值:
    使用案例:
    备注
    此方法创建的Element对象是一个全新的节点对象,如果要添加到document文档的节点下,需要配合Element的addChild(Element element)方法,创建的新节点才会添加到document文档里去。
    方法签名:
    Element createElement(String name, String value)
    描述:
    通过指定名称创建Element并且设置Element的值
    传入参数:
    返回值:
    使用案例:

    方法名称 方法简介
    Element getRootElement() 获取Document对象的根元素
    Element createElement(String name) 通过指定名称创建Element对象
    Element createElement(String name, String value) 通过指定名称创建Element并且设置Element的值
    TransformContext getContext() 获取当前DEE任务的上下文
    类型 说明
    com.seeyon.v3x.dee.Document.Element Element对象

    参数名 类型 说明
    name String Element名称
    类型 说明
    com.seeyon.v3x.dee.Document.Element Element对象
    Element table1 = 
     
    document.createElement("table1");//创建名称为“table1”的元素
    

    参数名 类型 说明
    name String Element名称
    value String Element值
    类型 说明
    com.seeyon.v3x.dee.Document.Element Element对象
    //创建名称为“table1”的元素,并且将元素的值设置为“hello world”
    
  • 备注:
    此方法创建的Element对象是一个全新的节点对象,如果要添加到document文档的节点下,需要配合
    Element的addChild(Element element)方法,创建的新节点才会添加到document文档里去。
    方法签名:
    TransformContext getContext()
    描述:
    获取当前DEE任务的上下文
    传入参数:
    返回值:
    使用案例:

    Element table1 =  document.createElement("table1","hello world");
    
    创建后此时将table1节点的xml内容:<table1>hello world</table1>
    

    类型 说明
    com.seeyon.v3x.dee.TransformContext 上下文
    TransformContext context = document.getContext()//获取当前DEE任务的上下文
    

2.2.2 Element操作

每个element含有两个属性,一个是attribute,一个是value,attribute是element的属性,value是值。

方法名称 方法简介
String getName() 获取当前元素的名称
Object getValue() 获取当前元素的值
void setValue(Object value) 设置当前元素的值
List getChildren() 获取当前元素的所有直接子元素
List getChildren(String name) 根据指定名称获取元素的直接子元素
List getAttributes() 获取当前元素的所有属性
Attribute getAttribute(String name) 根据指定名称获取当前元素的属性
Attribute setAttribute(String name, Object value) 设置当前元素的属性
Element getChild(String name) 根据名称获取当前元素的子元素
Element addChild(String name) 为当前元素增加指定名称的子元素
Element addChild(Element element) 为当前元素增加子元素
void removeChild(String name) 根据指定元素名称删除当前元素的子元素

方法签名
String getName()

描述
获取当前元素的名称。

传入参数

返回值

类型 说明
String 元素名称
****使用案例
String name = document.getRootElement().getName();//可以获取root元素的名称

方法签名
Object getValue()

描述
获取当前元素的值

传入参数

返回值

类型 说明
Object 元素的值
****使用案例
List<Element> list = document.getRootElement().getChild("table1").getChildren();//先获取table1的所有row元素
Object value = list.get(0).getChild("field1").getValue();//获取第一个row元素的子元素field1元素的值

方法签名
void setValue(Object value)

描述
设置当前元素的值

传入参数

参数名 类型 说明
value Object 元素的值
****返回值
类型 说明
void 没有返回值
****使用案例
List<Element> list = document.getRootElement().getChild("table1").getChildren();//先获取table1的所有row元素
list.get(0).getChild("field1").setValue("hello world");//获取第一个row元素的子元素field1,并且设置field1元素的值为"hello world"

方法签名
List getChildren()

描述
获取当前元素的所有直接子元素

传入参数

返回值

类型 说明
List 所有直接子节点Element对象的集合
****使用案例
List<Element> rows = document.getRootElement().getChildren();//可获取root节点下所有直接子节点(即表节点)

方法签名
List getChildren(String name)

描述
根据指定名称获取元素的直接子元素

传入参数

参数名 类型 说明
name String 子节点名称
****返回值
类型 说明
List 所有名称为name的直接子节点Element对象的集合
****使用案例
List<Element> rows = document.getRootElement().getChild("table1").getChildren("row");//可获取表table1节点下的
//所有节点名称为row的节点的集合

方法签名
List getAttributes()

描述
获取当前元素的所有属性

传入参数

返回值

类型 说明
List<Attribute> 元素所有属性的集合****使用案例
List<Attribute> attrs = document.getRootElement().getChild("table1").getAttributes();//获取表节点table1的所有属性

方法签名
Attribute getAttribute(String name)
描述
根据指定名称获取当前元素的属性
传入参数

参数名 类型 说明
name String 属性名称****返回值
类型 说明
com.seeyon.v3x.dee.Document.Attribute 元素属性对象
****使用案例
Attribute attr = document.getRootElement().getChild("table1").getAttribute("totalCount");//获取表节点table1的“totalCount”属性

方法签名
Attribute setAttribute(String name, Object value)

描述
设置当前元素的属性

传入参数

参数名 类型 说明
name String 属性名称
value Object 属性值
****返回值

|| 类型 | 说明 |
| :----------------------------------------------------------- | :--------------------------------- |
| com.seeyon.v3x.dee.Document.Attribute | 设置到元素的属性对象
使用案例: |

document.getRootElement().getChild("table1").setAttribute("id","888");//设置元素table1的属性id的值为“888”,
//如果元素table1没有属性“id”,那么会新建属性名称为“id”,值为“888”,并且设置给元素

方法签名
Element getChild(String name)

描述
根据名称获取当前元素的子元素

传入参数

参数名 类型 说明
name String 子节点名称
****返回值
类型 说明
com.seeyon.v3x.dee.Document.Element 子节点对象
****使用案例
Element table1 = document.getRootElement().getChild("table1");//获取根元素下的名称为table1的表元素

方法签名
Element addChild(String name)

描述
为当前元素增加指定名称的子元素

传入参数

参数名 类型 说明
name String 子节点名称
****返回值
类型 说明
com.seeyon.v3x.dee.Document.Element 子节点对象
****使用案例
Element root = document.getRootElement();
root.addChild("table2");//增加根节点的子节点,名称为“table2”

方法签名
Element addChild(Element element)

描述
为当前元素增加子元素

传入参数

参数名 类型 说明
element com.seeyon.v3x.dee.Document.Element 子节点Element对
****返回值
类型 说明
com.seeyon.v3x.dee.Document.Element 子节点对象
****使用案例
Element table3 = document.createElement("table3");//创建名称为“table3”的节点Element root = document.getRootElement();//获取根节点
root.addChild(table3);//将“table3”节点插入根节点下,作为根元素的子节点

方法签名
void removeChild(String name)

描述
根据指定元素名称删除当前元素的子元素

传入参数

参数名 类型 说明
name String 子元素名称
****返回值

使用案例

document.getRootElement().removeChild("table1");//删除根元素下名称为“table1”的子元素

2.2.3 获取rest接口结果

//  調用此方法可以获取到rest的结果
def restres = getRestResult(document);

2.2.4 获取webService接口结果

// document为dee内置参数,无需声明,可以直接使用 WSResult为token接口中返回的数据
def wsres = getParaByName(document,"WSResult");

三、使用前准备

3.1 环境准备

此说明文档基于DEE V4.1SP1和A8V7.1SP1版本,其他版本会有一些差别,大致的原理相同,具体请参考DEE官方的说明。

DEE下载地址:https://pan.baidu.com/s/1QNwY80fCvbm_8nhX2q9lrQ 提取码: 65gr
DEE授权码:请发起协同 DEE配置工具授权申请。

下载好DEE之后,解压运行SeeyonDEEToolInstall.bat进行安装,选择好自己的安装目录,一路下一步等等安装成功即可。

dee安裝

安裝成功之後,進入安裝目錄,运行startup.bat即可,会自动进入dee配置工具登录页面,初始密码为:123456,首次登录之后会进入到授权页面,选择好日期之后获取授权码,会下载一个dee.key的文件到本地,将此文件在协同中进行上传,注意协同的申请日期一定要和授权日期保持一致!

dee授权

dee授权申请

等待申请完成之后,复制下面的授权码到dee配置配置工具中点击确认,如果哦提示错误,请刷新页面重新登录后再输入(有可能掉线了)。

如果是伙伴或者客户需要申请,请联系对应的商务负责人帮忙申请授权。

授权完成后将进入到dee的首页面中。

dee首页

3.2 A8环境准备

A8请自行下载安装,本文档基于Mysql数据库讲解,如果其他数据库遇到配置数据源问题,请导入对应的jar包,重启DEE之后再测试。

A8安装完成之后,请登录到system账号,在系统设置->系统模块管理中启用数据交换引擎。

dee模块

如果是低版本的用户,请到SeeyonConfig.cmd中将dee.enable参数设置为true,然后重启A8,看到下图则说明启用成功。

a8 dee

3.3 数据源配置

DEE及A8安装完成并成功启动之后,登录到dee首页,进入基础设置-》数据源设置配置OA数据源,如果是导入此文档相关的DRP文件的用户请到里面修改为自己的数据库信息。

数据源配置

3.4 启用webservice

进入到A8安装目录/ApachejetSpeed/conf/SeeyonConfig.cmd ,插件参数中启用webservice接口。

启用ws

四、DEE参数

dee参数分为系统参数、全局参数、内置参数以及任务参数四种。

其中系统参数为dee的工具使用的参数,和任务无关,开发任务相关的为后面三种任务参数。

4.1 全局参数

全局参数将对所有的任务有效,无需再任务中单独进行配置,可以直接按照使用方法进行引用。

全局参数

全局参数主要用于很多任务都将用到的一些常量,例如和erp系统进行集成,那么我们可以配置一个属性为erp的接口ip地址,如:erp.ipAddress,那么我们在任务中即可直接用${erp.ipAddress}使用,注意,当我们切换环境的时候一定要修改此参数

4.2 内置参数

由于dee是和A8做了一定的关联,所以当dee任务用于A8的时候,会附带一些内置的参数,例如,将dee任务用于表单流程事件的时候,会默认附带一个masterId的参数,此id即当前任务执行所属表单的主表id。除此以外,A8还有一些其他的内置参数如下:

参数名 参数说明 备注
whereString 用于jdbc查询 调试不可用,部署的时候加上
A8orgPostId 当前人员的岗位id
A8orgDepartmentId 当前人员部门id
A8memberId
masterId 表单id 仅和表单有关联的时候可用
Paging_pageNumber 当前页数 列表查询用
A8memberName 当前人员姓名
A8memberCode 当前人员编号
A8loginName 当前人员登录名
Paging_pageSize 分页size
A8orgLevelId 职务级别id
flowId dee任务的id

4.3 参数使用

参数的使用将放到后续的各个任务中做介绍,这里不详细展开,大致用法为:${param}。

五、适配器

适配器分为输入、转换和输出适配器三种,分别用于数据来源,数据转换和数据的输出,这仅仅是DEE为了便于使用者了解做的区分,实际上你熟悉之后可以在一个适配器中完成所有工作,但是不建议这样操作,按照官方提供的分类做不同的功能更利于后期的维护。

在dee中,每个适配器实际上都对应了后台的一个java类,下面是一个对应关系,存放于dee-core.jar中:

适配器 类名
JDBC查询 JDBCReader.class
JDBC更新 JdbcUpdateAdapter.class
JDBC写入 JDBCWriter.class
SAP JCO SapJcoProcessor.class
rest接口适配器 RestServiceAdapter.class
webservice适配器 WsServiceAdapter.class
A8写入表单适配器 A8FormWriteWriter.class
A8发起表单适配器 A8BPMLauchFormColWriter.class
转换适配器 ModeMappingAdapter.class

5.1 JDBC查询

==关联任务:表单数据关联==

用于获取配置的数据源中的某个或者某几个表的数据,名称可以任意填写,为当前适配器的名称,建议规范命名,便于后期维护,数据源:选择已经配置的数据源即可。下放可以新建多个sql语句,名称为数据结构中的tableName,例如:

jdbc查询

上面图中的whereString是系统内置变量,用于jdbc查询,当导入OA系统后,会自动解析为where 1 = 1, 然后和人员选择的条件做关联,具体将在实际示例中讲解,参考:7.2表单字段关联

在调试界面可以+

看到运行的相关结果:

调试界面

数据结构示例如下:

<root>
    <member  count="10" totalCount="17">
        <row>
            <code>
            </code>
            <name>oa</name>
        </row>
        <row>
            <code>
            </code>
            <name>系统管理员</name>
        </row>
    </member>
</root>

5.2 JDBC更新

==关联任务:表单数据关联==

我们再输出配置中选择jdbc适配器的时候,会默认是执行jdbc更新操作,配置界面如下:

jdbc更新

其中表名是第三方数据库的表名,主键是用于判断是否有重复数据的字段,如果此字段有值,那么会更新此数据,如果此字段在数据库中不存在,那么会插入一条数据。

在使用jdbc更新(写入)时,需要再写入之前将dee的document转换成数据库需要的数据,例如,我将来源中的数据写入到org_member的EXT_ATTR_35、EXT_ATTR_36两个字段中,在配置来源的时候可以进行批量载入来源字段,载入之前我们需要先调试来源配置,才能够成功载入,调试可以勾选自己需要执行的适配器:

自定义小时

再次回到转换配置中,去配置字段映射,并且可以设置默认值,==(注意:截图中配置的为36/37两个字段,实际37字段为bigint,所以修改为了ext-attr-35,请注意)==配置如下:

字段映射

字段映射

配置完成之后,到调试栏目中勾选任务,执行调试:

jdbc更新调试

然后进入数据库,查看org_member的ext_attr_35和36字段已经成功被更新:

更新结果

5.3 JDBC写入

jdbc写入和jdbc更新相同,不再赘述,如果没有查询到主键id的值,则会插入数据。

5.4 Webservice高级适配器

==关联任务:根据人员code获取人员信息==

此适配器用于适配第三方的webservice接口。

5.4.1 ws接口获取信息

以产品的人员信息webservice接口为例,==http://ip:port/seeyon/services/{服务名}?wsdl==使用此接口前,请先配置好环境的webservice启用停用状况。(http://127.0.0.1/seeyon/services/personService?wsdl)。wsdl如下,贴出部分内容:

<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:ns="http://impl.organization.services.v3x.seeyon.com" xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:ns1="http://org.apache.axis2/xsd" xmlns:ax249="http://common.oainterface.seeyon.com/xsd" xmlns:ax248="http://infoParamImpl.organizationmgr.oainterface.seeyon.com/xsd" xmlns:ax245="http://services.ctp.seeyon.com/xsd" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:axis2="www.seeyon.com" targetNamespace="www.seeyon.com">
<wsdl:documentation>
This is the test webservice for Spring/Axis2 Integration
</wsdl:documentation>
<wsdl:types>
<xs:schema xmlns:ax250="http://common.oainterface.seeyon.com/xsd" attributeFormDefault="qualified" elementFormDefault="qualified" targetNamespace="http://infoParamImpl.organizationmgr.oainterface.seeyon.com/xsd">
<xs:import namespace="http://common.oainterface.seeyon.com/xsd"/>

<xs:element name="getPersonByCode">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" name="token" nillable="true" type="xs:string"/>
<xs:element minOccurs="0" name="personCode" nillable="true" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="getPersonByCodeResponse">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" name="return" nillable="true" type="ax251:PersonInfoParam_All"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

<wsdl:service name="personService">
<wsdl:port name="personServiceHttpSoap11Endpoint" binding="axis2:personServiceSoap11Binding">
<soap:address location="http://127.0.0.1:80/seeyon/services/personService.personServiceHttpSoap11Endpoint/"/>
</wsdl:port>
<wsdl:port name="personServiceHttpSoap12Endpoint" binding="axis2:personServiceSoap12Binding">
<soap12:address location="http://127.0.0.1:80/seeyon/services/personService.personServiceHttpSoap12Endpoint/"/>
</wsdl:port>
<wsdl:port name="personServiceHttpEndpoint" binding="axis2:personServiceHttpBinding">
<http:address location="http://127.0.0.1:80/seeyon/services/personService.personServiceHttpEndpoint/"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>

进入dee新建任务,来源配置中(不限于),选择webservice高级适配器,输入名称根据编号选择人员(自定义),点击下方的 wsdl,输入wsdl地址或者导入wsdlxml文件,点击解析,选择需要调用的方法会自动解析wsdl的 相关属性到上方信息中。

wsdl解析

由于我们系统的webservice接口调用需要用到token,那么我们再新增一个适配器获取token适配器,配置如下:http://127.0.0.1/seeyon/services/authorityService?wsdl

![ws token获取](images/ws token获取.png)

配置完成后,进入调试页面,勾选获取token适配器进行调试,查看调试结果,可以看到通过webService接口适配器调用的数据结果都会存放在全局参数document的参数WSResult里面:

ws调试结果

获取到的认证数据如下,我们需要采用脚本拿到自己需要的token信息,也就是下面数据中的id:

<?xml version='1.0' encoding='UTF-8'?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
    <soapenv:Body>
        <ns:authenticateResponse xmlns:ns="http://impl.services.v3x.seeyon.com">
            <ns:return xmlns:ax27="http://flag.common.ctp.seeyon.com/xsd" xmlns:ax212="http://constants.common.ctp.seeyon.com/xsd" xmlns:ax29="http://locale.util.sun/xsd" xmlns:ax24="http://domain.authenticate.common.ctp.seeyon.com/xsd" xmlns:ax25="http://util.java/xsd" xmlns:ax21="http://services.ctp.seeyon.com/xsd" type="com.seeyon.ctp.services.UserToken">
                <ax21:bindingUser xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
                <ax21:id>4725f295-b97f-4712-8eb5-00ece48bd58a</ax21:id>
            </ns:return>
        </ns:authenticateResponse>
    </soapenv:Body>
</soapenv:Envelope>

接下来,拿到了token webservice接口返回的数据之后,需要解析上面的数据,获取id,我们编写groovy脚本适配器解析token:

解析token

// 引入dom4j相关包解析ws result
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

// document为dee内置参数,无需声明,可以直接使用 WSResult为token接口中返回的数据
def wsres = getParaByName(document,"WSResult");
// 这边用包的全路径调用,以免和dee本身的document包冲突
org.dom4j.Document doc = DocumentHelper.parseText(wsres);
org.dom4j.Element root = doc.getRootElement();
def id = root.getStringValue().trim()
// 将token放到document参数中,方便后面调用
setParam(document, "token", id);

获取token完成之后,由于根据人员编号获取人员信息的webservice接口还需要一个code参数,那么我们采用任务参数进行配置,如果实际情况,可以由第三方传入,自定义的参数可以设置默认值,我这里设置为我系统中有的人员进行测试(test):

ws获取人员信息参数

配置完成参数之后,最后,我们还需要将这些参数映射到我们的获取人员信息的webservice接口中,我们采用==${参数名}==可以引用参数,配置如下:

ws参数映射

至此,我们通过webservice获取人员数据的接口已经开发完成,点击调试:

ws获取人员信息

<!-- 请求报文 -->
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:impl="http://impl.organization.services.v3x.seeyon.com">
    <soapenv:Header/>
    <soapenv:Body>
        <impl:getPersonByCode>
            <impl:token>
                <![CDATA[b194730f-b092-4c6c-8087-188e0dbe36b5]]>
            </impl:token>
            <impl:personCode>
                <![CDATA[test]]>
            </impl:personCode>
        </impl:getPersonByCode>
    </soapenv:Body>
</soapenv:Envelope>
<!-- 返回报文 -->
<?xml version='1.0' encoding='UTF-8'?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
    <soapenv:Body>
        <ns:getPersonByCodeResponse xmlns:ns="http://impl.organization.services.v3x.seeyon.com">
            <ns:return xmlns:ax248="http://infoParamImpl.organizationmgr.oainterface.seeyon.com/xsd" xmlns:ax245="http://services.ctp.seeyon.com/xsd" xmlns:ax249="http://common.oainterface.seeyon.com/xsd" type="com.seeyon.oainterface.organizationmgr.infoParamImpl.PersonInfoParam_All">
                <ax248:typeName>PersonInfoParam_All</ax248:typeName>
                <ax248:version>1</ax248:version>
                <ax248:birthday xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
                <ax248:departmentName>测试部</ax248:departmentName>
                <ax248:discursion>
                </ax248:discursion>
                <ax248:email>
                </ax248:email>
                <ax248:familyAddress xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
                <ax248:familyPhone xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
                <ax248:id>-7010601922930784264</ax248:id>
                <ax248:identity xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
                <ax248:mobilePhone></ax248:mobilePhone>
                <ax248:ocupationName>其他</ax248:ocupationName>
                <ax248:officePhone>
                </ax248:officePhone>
                <ax248:otypeName>客开</ax248:otypeName>
                <ax248:per_sort>1</ax248:per_sort>
                <ax248:personType>3</ax248:personType>
                <ax248:secondOcupationName xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
                <ax248:sex>-1</ax248:sex>
                <ax248:staffNumber xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
                <ax248:trueName>测试112</ax248:trueName>
                <ax248:accountId>670869647114347</ax248:accountId>
                <ax248:loginName>dsz</ax248:loginName>
                <ax248:passWord>~`@%^*#?</ax248:passWord>
                <ax248:personType>3</ax248:personType>
            </ns:return>
        </ns:getPersonByCodeResponse>
    </soapenv:Body>
</soapenv:Envelope>

我们可以对上述信息做处理,最终输出为我们想要的数据。

5.4.2 ws接口写数据

==关联任务:创建OA岗位==

上面我们介绍了如何通过webservice接口获取数据,接下来介绍如果通过webservice写入第三方。实际上大家可以发现,无论我们将webservice任务配置于输入还是输出,实际上本质来说都是接口的调用,所以和通过webservice接口读取数据没有本质的区别。

这边不再赘述,可以参考drp任务包中的任务:==DEE开发示例/3 webService接口/创建OA岗位==任务进行查看。

我们可以通过复制任务快速复用之前开发的内容,同样的还可以通过dee内部的一些任务调用方法,去调用一个公共的服务,例如上面讲到的获取token任务,可以作为一个公共服务。

下面是岗位创建成果的返回值,可以根据errorNumber判断是否成功。

<?xml version='1.0' encoding='UTF-8'?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
    <soapenv:Body>
        <ns:createResponse xmlns:ns="http://impl.organization.services.v3x.seeyon.com">
            <ns:return xmlns:ax259="http://services.ctp.seeyon.com/xsd" xmlns:ax262="http://ocupation.extparam.organizationmgr.impl.oainterface.seeyon.com/xsd" xmlns:ax263="http://infoParamImpl.organizationmgr.oainterface.seeyon.com/xsd" xmlns:ax264="http://common.oainterface.seeyon.com/xsd" type="com.seeyon.ctp.services.ServiceResponseImpl">
                <ax259:errorMessage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
                <ax259:errorNumber>0</ax259:errorNumber>
                <ax259:result>1374464720641999509</ax259:result>
            </ns:return>
        </ns:createResponse>
    </soapenv:Body>
</soapenv:Envelope>

5.5 Rest适配器

rest适配器用于调用第三方提供的rest接口使用,通常外部的rest接口都会涉及到认证,就需要先调用认证接口拿到token之后再调用后续接口,这边以A8的根据人员编号获取人员信息接口为例做示范(先获取token,解析之后再调用获取信息接口。

5.5.1 基础准备

首先,再创建任务之前,我们先做一些准备工作,先进入 A8system账号,创建一个rest账号,用于我们获取token(这个步骤一般是第三方提供)。

system创建rest账号

由于rest账号和密码不经常变更,并且可能多个接口都将使用到,所以我们将上面创建的rest账号密码保存到dee中,作为全局参数,新增三个全局参数(需要注意,切换环境的时候一定要修改):

restUser=dee
restPwd=xxxx
a8Url=http://127.0.0.1

定义全局参数

5.5.2 获取token

配置完参数之后 ,我们就可以新增一个dee任务,设置好名称,新增一个输入适配器,获取token:

token任务配置

配置完成后,可以看到我们的token接口采用post形式,含有两个参数,userName和password,即提交到post的body内容为:

{
    "userName" : "dee",
    "password" : "xxxxxx"
}

配置好任务后,我们进入调试界面,点击调试:

![token rest](images/token rest.png)

# 获取到的数据为
{  "bindingUser" : null,  "id" : "a8b526e8-5475-4536-b4f6-d778a6368ffa"}

5.5.3 解析token

拿到token数据后,我们再新建一个脚本适配器,用于解析获取token信息,groovy提供了非常强大简便的json解析方法JsonSlurper,下面的脚本就能将token存放到当前任务参数中:

==dee在调用rest接口的时候,会尝试将返回值填充到document中,如果填充失败,需要调用def restres = getRestResult(document);获取rest接口的返回信息。==

//  調用此方法可以获取到rest的结果
def restres = getRestResult(document);
// 将数据解析为json
// 调用groovy的json解析方法
def slurper =  new groovy.json.JsonSlurper();
def json = slurper.parseText(restres);
def token = json['id'];
// 将token放到参数中,用于下一个接口的调用
setParam(document, "token", token);

![解析rest token](images/解析rest token.png)

5.5.4 获取人员

最后,进入到正题,我们创建获取人员信息的接口,通过查看api,我们看到根据人员编号获取人员信息的接口为:==**http://ip:port/seeyon/rest/orgMember/code/{code}**==,请求方式为get,那么我们将code作为任务参数传入即可,同时token我们已经在上面的任务中获取到并放入任务参数中,所以我们可以直接引用:

rest接口配置

至此,rest接口配置完成,我们进入调试:

rest结果结果.png

5.6 存储过程

为了方便演示,在OA数据库中新增一个student的表:

CREATE TABLE `student`(
  `id` INT NOT NULL AUTO_INCREMENT COMMENT 'id',
  `name` VARCHAR(255) COMMENT '姓名',
  `sex` SMALLINT(6) COMMENT '性别  0-女 1-男',
  PRIMARY KEY (`id`)
);
insert into student(name,sex) values('张三', 1);
insert into student(name,sex) values('张翠花',0);

表创建完成之后,我们给此表创建一个简单的储存过程,用于根据id获取学生信息,输入参数为id:

DELIMITER $$
CREATE
    PROCEDURE `stu_info`(IN id INT)
    BEGIN
    SELECT NAME,SEX FROM student t WHERE t.id = id;
    END$$
DELIMITER ;

新建一个dee任务,在适配器中选择存储过程适配器:

存储过程适配器

存储过程

填写好存储过程名称之后,点击获取参数信息,可以解析相关参数,我们给id设置参数值为**${id}**,然后添加一个任务参数,用于填写id的值,开始调试。

执行存储过程

<!-- 存储过程返回参数 -->
<root>
    <form  count="1" totalCount="1">
        <row>
            <SEX>1</SEX>
            <NAME>测试1</NAME>
        </row>
    </form>
</root>

以上就是一个存储过程的简单示例。

5.7 SAP JCO(待编写)

见==SAP新增物料主数据==任务。

5.8 Groovy脚本

可以采用groovy进行编写脚本代码 ,可以引用A8相关的方法,也可以使用自定义的代码扩展。

这里不展开说,按照groovy语法进行编写即可。

可以参考5.5中的解析token。

5.9 自定义适配器

当dee提供的一些现场的适配器无法满足需求的时候,或者业务封装度较高的时候,我们可以采用自定义适配器功能。

在4.1sp1版本中存在一个bug,自定义适配器保存数据丢失,需要将包里面的7.1sp1工具端自定义适配器配置保存数据丢失问题补丁.zip补丁解压,拿到com文件夹以后,进入dee安装目录中DEE_Configurator\webapps\ROOT\WEB-INF\classes,进行部署重启。

需求:调用百度地图的开放API,根据名称和省份查询地址。

http://api.map.baidu.com/place/v2/search?query=ATM机&region=北京&output=json&ak=您的ak //GET请求

我们这边采用自定义适配器的方式实现,dee官方提供了相应的接口,我们只需要实现对应的接口即可:com.seeyon.apps.deeAdapter,核心方法为 :public Document execute(Document document) throws TransformException。

其中document是dee的执行上下文对象,包含了整个dee的相关内容。

首先,我们分析接口需要的参数为query(查询地址)以及region(所属地区),还有一个固定参数ak(百度地图的应用密钥),那么创建好dee任务之后,新增三个参数,分别存放所需的三个参数:

自定义适配器参数定义.png

接下来,我们开发自定义适配器,根据官方说明,我们新增一个类,实现dee的Adapter接口,主要处理逻辑如下:

package com.seeyon.apps.deeAdapter;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.seeyon.ctp.util.json.JSONUtil;
import com.seeyon.v3x.dee.Document;
import com.seeyon.v3x.dee.Document.Element;
import com.seeyon.v3x.dee.Parameter;
import com.seeyon.v3x.dee.Parameters;
import com.seeyon.v3x.dee.TransformException;
import com.seeyon.v3x.dee.adapter.Adapter;

/**
 * 百度地图适配器
 * @author fgw
 */
public class BaiduMapAdapter implements Adapter{
  
  private static final long serialVersionUID = 1L;

  /**
   * document是dee的内置对象,包含了整个dee的上下文
   * 根据query、region查询地址
   */
  @Override
  public Document execute(Document document) throws TransformException {
    // 通过上下文对象获取到 所有参数
    Parameters params = document.getContext().getParameters();
    // 查询地址
    String query = getParamValue(params.get("query"));
    // 所属区域
    String region = getParamValue(params.get("region"));
    // 百度地图开发api key,需要自己申请
    String ak = getParamValue(params.get("ak"));
    String url = "http://api.map.baidu.com/place/v2/search?query=" + query + "&region=" + region + "&output=json&ak=" + ak;
    System.out.println(url);
    URL mapUrl;
    try {
      mapUrl = new URL(url);
    } catch (MalformedURLException e1) {
      throw new TransformException("url格式有误:" + e1.getMessage());
    }
    HttpURLConnection conn;
    try {
      conn = (HttpURLConnection) mapUrl.openConnection();
    } catch (IOException e) {
      throw new TransformException("连接url异常" + e.getMessage());
    }
      BufferedReader in = null;
      conn.setDoOutput(true);
        conn.setDoInput(true);
        StringBuilder result = new StringBuilder();
        try {
      in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
    } catch (IOException e) {
      throw new TransformException("获取结果异常" + e.getMessage());
    }
        if(in != null){
            String line = "";
            try {
        while ((line = in.readLine()) != null) {
            result.append(line);
        }
      } catch (IOException e) {
        throw new TransformException("读取结果异常" + e.getMessage());
      }
        }
        String res = result.toString();
        // 解析json
        JSONObject jsonobject = JSONUtil.parseJSONString(res, JSONObject.class);
        int status = jsonobject.getIntValue("status");
        if(status == 0) {
          JSONArray array = jsonobject.getJSONArray("results");
          Document.Element rootElement = document.getRootElement();
          Document.Element table = rootElement.addChild("addresses");
          table.setAttribute("count", array.size());
          table.setAttribute("totalCount", array.size());
          for(int i = 0; i < array.size(); i++) {
            // 给document添加数据
            Element row = table.addChild("row");
            row.addChild("uid").setValue(array.getJSONObject(i).get("uid"));
            row.addChild("name").setValue(array.getJSONObject(i).get("name"));
            row.addChild("address").setValue(array.getJSONObject(i).get("address"));
            row.addChild("province").setValue(array.getJSONObject(i).get("province"));
            row.addChild("city").setValue(array.getJSONObject(i).get("city"));
            row.addChild("area").setValue(array.getJSONObject(i).get("area"));
          }
        } else {
          String message =  jsonobject.getString("message");
          Document.Element rootElement = document.getRootElement();
          Document.Element messageEle = rootElement.addChild("message");
          messageEle.setValue("获取数据错误:" + message);
        }
    return document;
  }
  
    /**
     * 获取参数中的内容
     */
  private String getParamValue(Parameter parameter) {
    if(null == parameter) {
      return "";
    }
    return parameter.getValue().toString();
  }
}

开发完成后,我们将class部署到dee中(DEE_Configurator\webapps\ROOT\WEB-INF\classes):

自定义代码部署.png

部署完成后,我们就可以新增自定义适配器,配置自己的代码了:

自定义适配器使用.png

<adapter  class="com.seeyon.apps.deeAdapter.BaiduMapAdapter"></adapter>

最后我们调试任务,先给query和region设置为天安门和北京:

自定义适配器输出.png

<root>
    <addresses  count="10" totalCount="10">
        <row>
            <uid>65e1ee886c885190f60e77ff</uid>
            <name>天安门</name>
            <address>北京市东城区长安街</address>
            <province>北京市</province>
            <city>北京市</city>
            <area>东城区</area>
        </row>
        <row>
            <uid>c9b5fb91d49345bc5d0d0262</uid>
            <name>天安门广场</name>
            <address>北京市东城区东长安街</address>
            <province>北京市</province>
            <city>北京市</city>
            <area>东城区</area>
        </row>
    </addresses>
</root>

我们再测试一下接口出错的返回值:

<root>
    <message>获取数据错误:Parameter Invalid</message>
</root>

完全可以根据代码自定义返回值。

上述功能就是自定义适配器的实现过程,我们还可以结果元数据,将任务导入到A8中进行表单数据关联等等。

5.10 A8接口

5.10.1 普通接口调用

在5.5rest接口中,我们已经以A8的接口作为了示例,是先进行了token获取,然后再调用了rest接口,这种方式普遍适用于第三方的rest接口,实际上,dee已经对A8的接口做了再封装,我们无需单独去获取token,仅需要填写好rest账号密码参数即可。

还是以获取人员信息为例z作一个简单说明,勾选A8内部接口,访问地址就无需附带token=token,配置好header中的userName和password即可:

A8接口配置

5.10.2 写底表

参考7.4 数据同步到底表

5.10.3 发起流程表表单

参考7.5 第三方发起流程表单

六、A8可采用dee任务的配置

6.1 表单数据关联

在应用定制平台-》设计中心表单制作页面,选择任何表单的业务关系,可以用于获取第三方数据,具体可以参考==7.2表单字段关联==。

OA数据关联

![DEE任务选择](images/DEE 关联任务选择.png)

6.2 表单触发设置

同表单数据关联,选择触发即可,还可以针对触发做一些配置。

6.3 流程事件

dee还可以应用于业务流程集成,进入单位管理员(集团版则是集团管理员)账号,在CIP集成平台中有业务流程集成菜单(注:此菜单需要采购业务流程集成插件)。

流程事件绑定

流程事件和节点事件要按照dee的开发规范去实现,具体案例参考7.3BPM事件触发

6.4 节点事件

同6.3流程事件,仅绑定位置不同。

6.5 交换引擎栏目

dee可以开发取数栏目,用于配置到A8中作为栏目展现,具体方式为,到空间设置中添加栏目,在扩展栏目中选择数据交换引擎栏目即可,具体参考7.7OA栏目空间

数据交换引擎栏目

七、DEE常见应用场景

7.1 对外发布业务接口

场景:新增dee任务,通过人员编号获取org_member中的人员信息.

首先,我们创建一个简单的任务,定义好任务编号:

对外接口发布.png

在jdbc来源中我们定义了一个code参数,这个是用于外部接口传入:

select * from org_member where code = '${code}'

配置完成后,我们将任务导入到A8中,就可以通过后面的远程接口进行调用了:

http://{ip}:{port}/seeyon/rest/dee/task/memberByCode

我们打开postman进行调试,注意所有的A8接口都需要先提前获取token,所以我这里先获取token,然后在进行调用接口:

地址配置.png

所有的接口都是post类型,我们将参数放到body中以json格式存放即可:

参数配置

点击send,获取返回结果:

dee接口返回值.png

7.2 表单字段关联

7.2.1 通过rest接口进行关联

需求:在OA的表单中有一张表单:部门人员信息表,选择部门后,可以获取到此部门下面的人员的相关信息:姓名、编号、生日、性别、职务、岗位。

部门人员信息

通过rest接口http:ip:port/seeyon/rest/orgMembers/department/${departmentId},即通过部门id获取部门所有人员。

7.2.1.1 获取rest数据

首先,创建rest接口适配器(参考5.5):

部门id获取人员

进行任务调试,可以看到获取到的数据如下:

# jsonArray格式,这边列出部分
{
    {
    "orgAccountId": 670869647114347,
    "id": -2093935450220510217,
    "name": "1124",
    "i18nNameId": null,
    "code": "",
    "createTime": 1574562401000,
    "updateTime": 1574562401000,
    "description": "",
    "orgLevelId": -8045575231964557737,
    "orgPostId": 5217476160474006035,
    "orgDepartmentId": -5679903276216857512,
    "second_post": [],
    "vjoinExternal": false,
    "v5External": false,
    "defaultGuest": false,
    "screenGuest": false,
    "preName": "",
    "i18nNameWithLocale": null,
    "dataI18nCategoryName": "organization.member.name"
  }
]

7.2.1.2 解析rest数据

调试完成后,我们获取到上面的数据格式,然后需要取出我们需要的数据(姓名、编号、生日、性别、职务、岗位),使用脚本进行数据解析:

/**
注意:此代码没有做容错处理
如果获取到数据为null需要返回空值
这里以有数据的部门为例
**/
//  調用此方法可以获取到rest的结果
def restres = getRestResult(document);

// 将数据解析为json
// 调用groovy的json解析方法
def slurper =  new groovy.json.JsonSlurper();
// 获取到的结果是一个jsonARRAY 数组
def jsonArray = slurper.parseText(restres);

// 创建一个document 的xml  根据xml格式创建
StringBuffer  sb = new StringBuffer();

sb.append("<root><member");
sb.append(" count=\"" +jsonArray.size() + "\"  totalCount=\"" + jsonArray.size() + "\">");

for(int i = 0; i < jsonArray.size(); i++) {
   
  def member = jsonArray[i];
  // 获取需要的信息  姓名、编号、岗位、职务、性别、生日
  def name = member['name'];
  def orgPostId = member['orgPostId'];
  def orgLevelId = member['orgLevelId'];
  def birthday = member['birthday'];
  def gender = member['gender'];
  def code = member['code'];
  
  //拼接每一行的xml数据
  sb.append("<row><name>" + name + "</name>");
  sb.append("<orgPostId>" + orgPostId + "</orgPostId>");
  sb.append("<orgLevelId>" + orgLevelId + "</orgLevelId>");
  sb.append("<birthday>" + birthday + "</birthday>");
  sb.append("<gender>" + gender + "</gender>");
  sb.append("<code>" + code + "</code></row>");

}

sb.append("</member></root>");
// 采用xml生成document
document = createDocByXml(sb.toString());

脚本编写完成后,我们勾选两个任务,再次调试,查看document数据是否符合预期:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <member  count="9" totalCount="9">
        <row>
            <name>测试112</name>
            <orgPostId>5217476160474006035</orgPostId>
            <orgLevelId>-8045575231964557737</orgLevelId>
            <birthday>1588780800000</birthday>
            <gender>-1</gender>
            <code>test</code>
        </row>
    </member>
</root>

7.2.1.3 数据字典转换

可以看到上面已经将我们的rest接口数据转换成了dee适配的document数据,但是其中的职务、岗位、性别及生日都显示为A8的格式(id或者时间戳),那么我们需要进行转换,新增一个转换适配器。

在新增转换适配器之前,要先到dee基础设置/字段管理中新增相关的转换方式,对于性别,我们可以直接采用枚举转换,由A8系统得知,性别字段-1为未知,1是男,2是女,那么我们新建配置如下:

性别映射

职务级别和岗位分别对应了职务表和岗位表中的id,那么我们创建两个JDBC数据字典:

岗位映射

数据字典配置完成之后,我们再回到任务中,在转换适配器中新增批量配置,选中需要转换的相关数据,配置如下,注意,即使无需转换的数据依然要选中

映射配置

配置完成后,再次执行任务调试,可以成功看到转换后的数据:

<root>
    <member  count="9" totalCount="9">
        <row>
            <name  display="name">测试112</name>
            <orgPostId  display="orgPostId">其他</orgPostId>
            <orgLevelId  display="orgLevelId">客开</orgLevelId>
            <birthday  display="birthday">1588780800000</birthday>
            <gender  display="gender">男</gender>
            <code  display="code">test</code>
        </row>
    </member>
</root>

数据转换成功后,我们进入到元数据页签,点击自动载入元数据,修改表名和字段名称,以及字段长度进行保存:

人员信息载入元数据

至此,我们的任务基本开发完成,至于对日期的转换,可以自行尝试用脚本转换。将任务导入到A8系统中(7.9任务导入导出),然后进入对应的应用表单中进行配置。

登录表单管理员账号,到dee应用中,找到部门人员信息表,业务关系中点击加号,新建关联:

人员信息任务配置

数据关联配置

运行效果如下:

人员信息任务执行

人员信息选择结果

实际的情况会比这个复杂,还要处理分页、查询等,以上就是通过rest接口获取的一个简单示例。

查询的参数会封装到参数中,可以自己进行解析,然后传入接口中,可以在A8中运行后到dee控制台的日志中查看。

7.2.2 通过JDBC获取数据

通过JDBC进行数据关联,系统会自动识别参数并且筛选,只需要添加$whereString参数即可。

首先,创建一个JDBC输入适配器,根据部门id获取部门人员

/* 这里的${departmentId}是任务参数 ,为防止报错,设置默认值 -1 */
SELECT name, code, org_post_id, org_level_id, ext_attr_11 AS sex,  ext_attr_21 AS birthday FROM org_member where org_department_id = ${departmentId}

创建完成后,进行调试(给deparmentId设置一个数据库中存在人员的id,方便自动载入元数据):

<root>
    <member  count="10" totalCount="17">    
        <row>
            <org_level_id>-8045575231964557737</org_level_id>
            <birthday>2020-05-07 00:00:00.0</birthday>
            <code>test</code>
            <sex>1</sex>
            <name>测试112</name>
            <org_post_id>5217476160474006035</org_post_id>
        </row>
    </member>
</root>

获取到数据后,我们可以使用之前的枚举转换进行数据转换,同样的,我们也可以在sql来源的时候处理相关数据:

SELECT m.name, m.code, p.name AS postname, l.name AS levelname, ext_attr_11 AS sex,  ext_attr_21 AS birthday FROM org_member m
LEFT JOIN org_post p ON p.id = m.ORG_POST_ID
LEFT JOIN org_level l ON l.`ID` = m.ORG_LEVEL_ID
<root>
    <member  count="10" totalCount="17">
        <row>
            <birthday>2020-05-07 00:00:00.0</birthday>
            <postname>其他</postname>
            <code>test</code>
            <sex>1</sex>
            <name>测试112</name>
            <levelname>客开</levelname>
        </row>
    </member>
</root>

上面已经是我们最终需要的数据结果,然后回到元数据页面,点击自动载入元数据,修改表名和显示名称,填充好字段长度,保存。

最后,我们给我们的sql语句加上条件,导入到A8中进行绑定,这里的 $whereString调试的时候需要去掉,但是导入A8前需要加上才能进行筛选:

SELECT m.name, m.code, p.name AS postname, l.name AS levelname, ext_attr_11 AS sex,  ext_attr_21 AS birthday FROM org_member m LEFT JOIN org_post p ON p.id = m.ORG_POST_ID LEFT JOIN org_level l ON l.`ID` = m.ORG_LEVEL_ID ${whereString} and m.org_department_id = $departmentId

导入任务后,发现筛选不起作用,原因是因为dee的sql仅支持简单的sql进行查询,所以我们需要对上面的sql语句进一步封装:

select t.* from (SELECT m.name, m.code, p.name AS postname, l.name AS levelname, ext_attr_11 AS sex,  ext_attr_21 AS birthday FROM org_member m LEFT JOIN org_post p ON p.id = m.ORG_POST_ID LEFT JOIN org_level l ON l.`ID` = m.ORG_LEVEL_ID where m.org_department_id = $departmentId) t $whereString

数据关联筛选jdbc

7.3 BPM事件触发

BPM事件主要包含两种类型:

  1. 流程处理事件:针对于整个流程的相关事件,包含发起、撤销、终止以及结束,其中除了结束事件,其余三个都分为了前和后(发起前,发起后),可以理解为入库前和入库后两个操作,需要注意的是使用了发起后事件,需要从数据库取数,而不是上下文中取得表单数据,因为后事件任务都是异步执行,无法获取到上下文数据。

  2. 节点处理事件:针对于某个节点的 相关事件,包含处理、回退、取回,同样的这三种都含有前和后,同样需要注意不要从数据库直接取数。

节点处理事件

流程处理事件

7.3.1 流程前事件

对于前事件(发起前、撤销前、终止前、回退前、取回前),所有的前事件都可以阻塞,主要用的场景是做一些校验、判断等(如果要写数据,不推荐用前事件,请采用超级节点,如果写数据,容易造成事务不一致)。这里用发起前事件为例,其余的都一样,仅仅是绑定的地方不同而已。

需求:我们每个部门需要发起外部人员审批单,外部人员用于兼职发传单,对年龄有要求,不能超过20岁(主要针对于在校大学生),如果有超过20岁的,则不能发起申请。

针对上面的需求,分析之后发现是对表单的数据做一定的校验,那么我们采用发起前事件进行实现,流程表单相关字段如下:

兼职申请流程表.png

首先,我们创建一个dee任务,对于前事件,我们一般都采用脚本的形式,用于获取表单上的数据,根据年龄条件进行判断。

// 由于我们暂时不确定输出的document格式,所以我们先调用dee提供的方法,将formdata转换成document,先导入A8中执行,然后再进行解析(dee本身有模拟方法 ,但是4.1sp1版本存在一些bug,所以采用这种方式)
formDataToDoc();

编写(旁边有很多方法可以直接双击调用)好脚本之后,我们导入OA,先进行配置,登录OA单位管理员(集团版为集团管理员),进入CIP集成平台业务流程集成(需要采购插件),新建集成业务,选择我们刚刚的兼职人员申请表:

新建集成业务-发起前.png

集成业务定义.png

设置好关联应用,新增流程事件绑定:

设置好关联应用.png

流程事件绑定dee.png

选择好事件类型,处理方式选择dee,选择处理事件,点击保存,然后我们登录OA账号,去发起刚刚配置的流程,再回到dee控制台查看任务执行情况。

发起前任务执行情况.png

<root>
    <formmain_0090>
        <row>
            <id>9190683270163417496</id>
            <field0001  display="部门名称">-5679903276216857512</field0001>
        </row>
    </formmain_0090>
    <formson_0091  count="3" totalCount="3">
        <row>
            <id>-6615732749316829606</id>
            <field0009  display="姓名">张三</field0009>
            <field0011  display="编号">1</field0011>
            <field0012  display="生日">2020-05-08</field0012>
            <field0013  display="性别">
            </field0013>
            <field0014  display="年龄">20</field0014>
            <field0015  display="备注">
            </field0015>
            <formmain_id>9190683270163417496</formmain_id>
            <sort>1</sort>
        </row>
    </formson_0091>
</root>

我们现在拿到了数据格式,就可以进一步的使用脚本进行解析,并对每一个兼职人员做出判断:

// 改造后的groovy脚本
// 将表单数据生成到document中
document = formDataToDoc();
// 解析document,请注意修改其中的 formson_key   我这里是 formson_0091,请修改为自己的表名
def tableName = "formson_0091";
// 使用内置的方法将document转换成 list
def formson = docToListMapByTable(document,tableName);
if(formson.size() == 0) {
  // 如果没有填写  提醒用户填写数据
 setHighSetStr(document,"F","Y","请填写兼职人员",""); 
} else {
  // 用于记录是否有超员的人
  def res = false;
  // 遍历 判断年龄
  StringBuffer msg = new StringBuffer();
  msg.append("<![CDATA[<table border='1'><th>姓名</th><th>原因</th>");
  formson.each{
    // 这里是根据表单写死了,实际上应该先根据display将所有的字段作为一个集合,这里动态取最好,或者在前面进行参数定义,便于维护
    def age =  Integer.valueOf(it.get("field0014"));
    if(age  > 20) {
      def name = it.get("field0009");
      res = true;
      msg.append("<tr><td>" +  name + "</td><td>年龄大于20,无法申请!</td></tr>");
    }
  }
  msg.append("</table>]]>");
    if(res) {
      // 有人超过年龄
      setHighSetStr(document,"F","Y",msg.toString(),"");
    } else {
      setHighSetStr(document,"T","Y","申请成功,所有人符合标准","");
    }
}

导入到A8后再次执行,可以看到李四的年龄超过二十,所以会进行提示:

发起前校验运行效果.png

7.3.1.1 setHighSetStr用法

上图中会弹出提示信息,我们只需要调用==setHighSetStr(document,status,ispop,reason,remark);==方法即可。

要使用开发高级功能,需要设置开发高级字符串
参数:当前document对象,status状态(T:成功/F:失败),ispop是否弹窗(Y:弹出/N:不弹),操作原因(reason),备注(remark)
返回值:包含开发高级字符串的document
注意:目前产品有bug,ispop设置Y但是status为T的时候不会弹窗。
cap4取消了弱阻塞,也就是status为T,但是ispop为Y的情况。

7.3.2 流程事件触发

需求:在上面的表单基础上,流程通过以后,我们将重复表的数据记录到数据库的表中,并且生成一个申请编号回填到表单上。

申请表-触发.png

实现此功能前,我们先到OA的数据库中执行下面的sql语句,创建数据库表,我们将数据写入到此表中:

CREATE TABLE `part_member`(  
  `id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
  `bianhao` VARCHAR(255) NOT NULL COMMENT '申请编号',
  `name` VARCHAR(20) COMMENT '姓名',
  `code` VARCHAR(20) COMMENT '编号',
  `birthday` VARCHAR(20) COMMENT '生日',
  `sex` VARCHAR(20) COMMENT '性别',
  `age` VARCHAR(20) COMMENT '年龄',
  `memo` VARCHAR(255) COMMENT '备注',
  PRIMARY KEY (`id`)
) ENGINE=INNODB CHARSET=utf8 COLLATE=utf8_general_ci;

对于任务的配置方式,我们有两个可以可以配置:

  1. 在业务关系中-新建触发

  2. 通过流程事件(节点事件)进行配置。

采用第一种触发的方式,由于是异步,我们无法获取到表单的数据,所以需要通过数据库进行操作,就会用到一个默认的参数$masterId.

select formson_0093.field0009,formson_0093.field0011,formson_0093.field0012,formson_0093.field0013,formson_0093.field0014,formson_0093.field0015 from formson_0093 where formmain_id = $masterId

实现了此步骤之后,我们的目标是将数据写入到第三方的数据库表中,那么调用JDBC适配器进行数据写入,(参考5.3JDBC写入),写入之前,我们需要先调用字段映射,我们批量载入来源字段的时候,可以直接从CAP4模板中选择:

表单字段映射选择.png

表单映射结果.png

这里的编号在第一个转换document的时候生成了编号,并且添加到了参数中,这边直接引用,后面可以回填到表单上。

JDBC写入配置十分简单:

jdbc写入.png

最后,我们新增一个回写表单数据的适配器即可:

回写配置.png

至此,我们的数据存档及数据回写到表单中就完成了,我们导入到A8中进行测试,配置到触发中。

流程触发配置.png

配置在触发中的好处是,我们设置触发条件。

然后我们配置了结束后,我们进行测试:

表单数据

成功写入数据库.png

我们再回到已发中,点击刚刚的单据,可以看到申请编号也成功的回填:

申请编号回填.png

==注意点:此功能中,我们使用了masterId这个内置参数,在DEE配置到A8的表单中运行的时候,都会附带一个masterId的参数,此参数即主表的id,回填的时候实际上也是根据这个参数进行数据的更新操作。==

7.3.3 通过JDBC更新回填

上面一个流程事件触发我们采用了标准的表单数据回填功能,实际上他的原理是通过masterId进行数据的更新,所以我们也可以使用JDBC的方式对数据进行更新,这边不展开说明,可参考5.2JDBC更新

7.4 超级节点

前面介绍了流程事件,流程前事件主要是用于校验,如果有写数据的操作,我们推荐采用超级节点,超级节点主要解决的问题就是数据不一致的问题,A操作表示提交表单数据,B操作表示写入第三方数据,如果B提交成功而A失败了,那么再次提交会导致B重复,或者采用流程后事件,A成功了,但是B失败了,也会造成双方数据不一致,为了解决这个问题,推出了超级节点,将A/B操作分为两个节点分别处理,如果B失败,可以回退到A继续处理,或者B正常后再次处理,如果A失败,则不会提交到B,保证了事务的一致性。

需求:依然以上面的表单作为示例,我们将数据写入到表中(part_member,创建表语句在7.3.2中),上面说了如果涉及写数据,我们最好是采用超级节点的方式。

首先,我们创建一个数据获取任务(和上面基本相同,这里不再详细说明),任务完成后,我们到单位管理员中配置超级节点

超级节点配置.png

配置完成后,我们将超级节点添加到流程中:

超级节点流程配置.png

最后,发起流程进行测试:

第一次测试我在输入适配器脚本中直接抛出了异常,dee任务会执行失败:

throw new Exception("执行失败!");

dee回退流程.png

然后我们去掉抛出的异常,再次执行任务:

超级节点执行.png

超级节点写入数据

7.4 数据同步到底表

DEE内置了底表数据同步的功能,一般使用场景是第三方的数据定时同步到A8系统中,可以和定时器结合起来做定时同步。

需求:将OA中的部门和人员自动同步到底表中。

部门人员信息

可以看到上面的表单是主从表关系,那么我们的dee也会存在主从关系,首先我们先创建数据来源,数据来源为A8的人员和部门表即org_member和org_unit表,新增输入适配器(JDBC):

主从表jdbc.png

配置完输入适配器后,我们调试任务(列出部分内容):

<root>
    <dept  count="10" totalCount="112">
        <row>
            <code>B099</code>
            <name>风险评审组(金融板块)</name>
            <id>-9080107090372179496</id>
        </row>
    </dept>
    <member  count="14" totalCount="14">
        <row>
            <birthday  dee_isNull="true">
            </birthday>
            <postname>其他</postname>
            <code>
            </code>
            <sex>-1</sex>
            <deptId>-5679903276216857512</deptId>
            <name>1121</name>
            <levelname>客开</levelname>
        </row>
    </member>
</root>

然后我们需要将上面的数据转换成A8识别的表单数据,到转换适配器中新增转换,选择批量配置,并且勾选应用到A8表单:

应用到A8.png

我们在来源中自动载入document的所有字段,然后目标字段我们从无流程表单中选择我们对应的部门人员信息表,映射如下:

选择A8表单配置好映射关系.png

需要注意的是,由于我表单中没有设置主从关联关系(即从表中没有人员所属部门id),我这边暂时以岗位字段作为代替:

转换适配注意点.png

我们将转换配置完成后,再次执行调试任务,取得下面的结果:

<root>
    <formmain_0086  count="10" totalCount="112">
        <row>
            <部门名称 display="部门名称">-9080107090372179496</部门名称>
            <部门编号 display="部门编号">B099</部门编号>
        </row>
    </formmain_0086>
    <formson_0087  count="14" totalCount="14">
        <row>
            <姓名 display="姓名">oa</姓名>
            <编号 display="编号">
            </编号>
            <生日 display="生日">
            </生日>
            <性别 display="性别">未知</性别>
            <职务 display="职务">其它</职务>
            <岗位 display="岗位">-697613953233630315</岗位>
        </row>
    </formson_0087>
</root>

最后,我们去配置输出适配器,同步到A8表单中,同步之前,我们需要先给表单设置编号,我这里设置为deptMember :

编号设置.png

选择需要同步的表单.png

我们再配置表单编号的时候,一定要通过选择去进行选择,这样下面配置外键主键的时候就可以自行选择了。

rest接口同步数据配置.png

注意,上面的地址、账号这里存在bug,暂时无法使用变量,所以我们填写好服务ip即可。

最后执行调试:

同步结果.png

注意:在DEE中调试的时候,会自动进行分页,数据可能存在不全的情况,所以可以导入A8中进行执行获取全量数据。

{
  "successINFO": {
    "formmain_0086": [
      {
        "部门名称": "-9080107090372179496"
      },
      {
        "部门名称": "-9071982903700861476"
      }
    ]
  },
  "errorINFO": {},
  "errMsg": [],
  "message": "数据执行成功:10 条,数据执行失败: 0 条"
}

同步成功后,我们进入A8底表查看同步结果:

表单数据.png

7.5 第三方发起流程表单

此功能常见应用于自动读取第三方数据发起流程表单,也可以将dee任务发布为rest接口供第三方调用。

需求:自动读取org_member表中的数据批量发起部门兼职人员申请表。

实现此功能前,我们需要先给部门人员申请表配置模板编号,我这里设置为partMember。

流程表单模板编号.png

首先,我们同样先获取数据来源,这里依然用JDBC为例,获取部门和人员数据:

dept : select id, name, code from org_unit where type = 'Department' 

member : SELECT m.org_department_id as deptId, m.name, m.code, p.name AS postname, l.name AS levelname, ext_attr_11 AS sex,  ext_attr_21 AS birthday FROM org_member m LEFT JOIN org_post p ON p.id = m.ORG_POST_ID LEFT JOIN org_level l ON l.`ID` = m.ORG_LEVEL_ID where m.is_admin = 0 

同样的调试任务之后新增转换配置,载入来源,需要注意和无流程表单不同的是,我们的流程表单发起的时候需要配置一个转换是表单的发起人,这边设置默认值:

配置登录名.png

同时,还要将数据转换成流程表单的格式,新增一个转换适配器:

表单数据格式转换.png

最后,我们到输出配置中新增批量发起流程的适配器:

批量发起流程配置.png

最后,我们进行调试,请注意,在dee中调试无法获取到所有数据,请导入A8之后可以获取全量数据:

批量发起流程结果.png

再进入OA中,查看刚刚发起的流程:

待办栏目.png

流程表单数据查看.png

从上面的过程来看,和无流程表单基本一致,只需要多一步转换成xslt格式,并且映射字段的时候增加一个登录名字段的 映射即可。

这里的数据来源为jdbc,实际上还可以和webservice、rest接口等结合起来,加上自己的脚本做处理,都能够成功发起。

7.7 OA栏目空间(BUG)

实现OA的栏目空间,我们需要到dee中开发一个取数任务。

需求:在OA中增加一个栏目,用于展示员工的生日信息。

首先,使用jdbc查询适配器,获取系统中人员的生日信息,在我们系统中,人员生日存放于ext_attr_21字段,所以编写sql如下:

select name , ifnull(ext_attr_21, '生日未填写') as birthday from org_member where is_admin = 0

创建好输入适配器之后,点击调试,调试成功后,回到元数据页签,点击自动载入元数据,可以修改表明陈和字段显示名称为中文:

载入元数据

配置完成后,我们将任务导入到OA中,然后进行栏目配置。

7.8 定时任务配置(日志清理)

可以在dee的定时任务配置中配置完成导入OA,也可以配置好之后导入OA再修改定时任务配置。

我们以日志清理任务为例,我们创建一个日志清理的dee任务:

clearLogs(7); // 表示清除7天以前的日志

进入dee的定时器管理界面,选择需要配置的定时任务设置好对应的执行策略即可:

定时器设置.png

定时任务配置.png

配置完成后,我们将任务导入OA即可,同时也可以在OA中进行修改:

![A8 dee定时器设置.png](images/A8 dee定时器设置.png)

==请务必注意:对于要在A8里面执行的定时任务,一定要在DEE中导入到A8之后,取消dee里面的定时任务,否则当dee运行的时候依然会执行dee中配置的定时任务,就会出现重复执行的情况,特别是针对于定时批量发起表单这种任务!!!==

7.9 任务导入导出

在DEE中将任务开发完成之后,我们勾选对应的dee任务,点击导出(可以批量导出),会下载一个drp的任务包。

导出任务

导出成功后,有两种方式可以部署:

  1. 我们直接将导出的dee.drp文件放到base\dee\hotdeploy目录下,系统会执行定时任务自动加载。

  2. 登录单位管理员(集团版为集团管理员)账号,找到CIP集成平台中的数据交换菜单,到DEE控制台中
    择任务管理页签,导入任务即可。

    admin1导入

八、常用技巧

8.1 任务参数

任务参数是直接拼接的,例如a8Url/seeyon,系统会自动解析前面的{a8Url}/seeyon,系统会自动解析前面的

8.2 内置参数

dee和表单集成的情况十分多,一定要灵活运用masterId。

8.3 善用任务复制

很多时候,我们的任务都是可以复用的,可以通过复制任务提升开发效率。

8.4 groovy使用

由于大多数人是java开发,所以编写脚本的时候都会局限于java,但是实际上groovy本身提供了想当多的好用的方法及函数,可以直接使用,例如使用JsonSlurper解析json:

//  調用此方法可以获取到rest的结果
def restres = getRestResult(document);
// 将数据解析为json
// 调用groovy的json解析方法
def slurper =  new groovy.json.JsonSlurper();
def json = slurper.parseText(restres);
def token = json['id'];
// 将token放到参数中,用于下一个接口的调用
setParam(document, "token", token);

8.5 任务调试技巧

在DEE的调试中,我们可以在调试界面勾选自己想要执行的任务,查看document的执行结果,实际在上面的很多任务中都对分步调试有了演示,这里不展开说明。

当任务导入到A8中,如果遇到执行失败,或者我们想要查看任务的执行过程,我们同样可以进入单位管理员的数据交换->DEE控制台中查看每一步的执行结果,包含document信息、rest接口数据、webService接口数据以及参数等,如下图:

任务执行监控.png

执行异常信息.png

参数及各步骤document查看.png

8.6 自定义java类

我们可以到dee中编写自定义的java类处理一些共用逻辑。

同时也可以在脚本中调用A8的一些工具类,但是需要导入到A8之后才能成功运行。

0

评论区