Commit f5d5dc1f authored by custom's avatar custom

Merge origin/master into master

parents d06f5846 19324914
# Logs # Logs
logs logs
*.log *.log
# Runtime data # Runtime data
pids pids
*.pid *.pid
*.seed *.seed
# Directory for instrumented libs generated by jscoverage/JSCover # Directory for instrumented libs generated by jscoverage/JSCover
lib-cov lib-cov
# Coverage directory used by tools like istanbul # Coverage directory used by tools like istanbul
coverage coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt .grunt
# Compiled binary addons (http://nodejs.org/api/addons.html) # Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release build/Release
# Dependency directory # Dependency directory
# Deployed apps should consider commenting this line out: # Deployed apps should consider commenting this line out:
# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
node_modules node_modules
_book/ _book/
book.pdf book.pdf
book.epub book.epub
book.mobi book.mobi
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
]]></script> ]]></script>
``` ```
####设置字段只读属性 #### 设置字段只读属性
>Hel.setReadonly(id,flag); >Hel.setReadonly(id,flag);
第一个参数id为字段的id,第二个参数flag为true时将该字段设为只读,为false时将该字段设为可输入。 第一个参数id为字段的id,第二个参数flag为true时将该字段设为只读,为false时将该字段设为可输入。
```html ```html
......
### 锁屏和解屏
#### 锁屏
>Hel.mask(opts);
其中参数为:
属性名 | 类型 | 含义
-------- | ----- | -----
id| String(选填) | 当前窗口的id,缺省值为body
```
Hel.mask(); //缺省值为body
Hel.mask({
id:'',//传入相应的参数
});
```
#### 解屏
```
Hel.unmask();
```
...@@ -62,7 +62,6 @@ size| String(必填) | 右弹窗口的宽度(THIRD/HALF/FULL/任意px像素) ...@@ -62,7 +62,6 @@ size| String(必填) | 右弹窗口的宽度(THIRD/HALF/FULL/任意px像素)
### 打开右弹抽屉通用方法
#### 打开抽屉 #### 打开抽屉
> Hel.openBox()函数接收一个对象,对象属性含义如下: > Hel.openBox()函数接收一个对象,对象属性含义如下:
......
## Excel文件批量导出
本系统支持Excel文件导出排队处理,支持超大数据量,能实时查询文件生成情况,支持取消导出。
#### 使用方法
1. 在代码中注入Excel导出Service
```java
@AutoWired
private ExcelExportServiceImpl excelService;
```
2. 在自己的controller中调用方法
```java
excelService.saveExportInfo(sqlId,iRequest,config,rowMaxNumber);
```
| 参数名 | 类型 | 描述 |
| :--: | :--: | :--: |
| sqlId | String | mybatis对应的数据库查询语句的ID |
| iRequest | IRequest | 带有上下文信息的reuqest对象 |
| config | ExportConfig | 包含Excel各列信息的对象,由前台传递json对象转换而来 |
| rowMaxNumber | int | 生成Excel的最大行数,可使用重载方法,不包含次参数默认为1,000,000 |
3. 运行Excel导出程序(获取该程序,请联系部门相关负责人)
```shell
java -jar hel-batch-parent.jar &
```
4. Excel导出情况查询
用户若是为管理员身份,则查询全部人的导出情况,否则只显示当前用户的导出数据
```java
excelService.queryExportInfo(status, iRequest);
```
| 参数名 | 类型 | 描述 |
| :--: | :--: | :--: |
| status | String | 指定Excel导出的状态,该参数可为空查询全部状态数据<br><ll><li>new->等待</li><li>generating->文件导出中</li><li>finished->导出完成</li><li>failed-> 导出失败</li></ll> |
| iRequest | IRequest | 带有上下文信息的reuqest对象 |
5. 下载已完成的Excel文件
```java
excel.downloadExcel(filePath, fileName, request, response);
```
| 参数名 | 类型 | 描述 |
| :--: | :--: | :--: |
| filePath | String | 需要下载的文件的路径 |
| fileName | String | 想要保存的文件的名称 |
| request | HttpServletRequest | 用户的请求对象 |
| response | HttpServletResponse | 用户的响应对象 |
**下载前,需要检查config.properties文件中的export.offerUrl配置项是否配置正确,且为hel-batch-parent.jar提供的文件下载url**
\ No newline at end of file
# hlsDatePicker标签 # hlsDatePicker标签
日期选择器(不包含时分秒) 日期选择器(不包含时分秒)
xml配置 xml配置
``` ```xml
<!--基本用法--> <!--基本用法-->
<h:hlsDatePicker id="hlsDatePickerId" bind="enabled:isEnabled,value:data.value" placeholder="hlsDatePicker"/> <h:hlsDatePicker id="hlsDatePickerId" bind="enabled:isEnabled,value:data.value" placeholder="hlsDatePicker"/>
``` ```
......
# RabbitMq消息队列 # RabbitMq消息队列
## 1.简介 ## 1.简介
融租易 的消息系统提供`唯一``广播`以及`主题`三种模式,实现方式为 RabbitMQ。 融租易 的消息系统提供`唯一``广播`以及`主题`三种模式,实现方式为 RabbitMQ。
### 1.1 RabbitMQ介绍 ### 1.1 RabbitMQ介绍
RabbitMQ 是实现 AMQP(高级消息队列协议)的消息中间件的一种,用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。消息中间件主要用于组件之间的解耦。rabbitmq多应用于批量数据异步处理、并行任务串行化,高负载任务的负载均衡等 重量级,高并发,异步高可靠性场景。 RabbitMQ 是实现 AMQP(高级消息队列协议)的消息中间件的一种,用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。消息中间件主要用于组件之间的解耦。rabbitmq多应用于批量数据异步处理、并行任务串行化,高负载任务的负载均衡等 重量级,高并发,异步高可靠性场景。
### 1.2 配置rabbit ### 1.2 配置rabbit
(1)用户要先在 `applicationContext-rabbitmq.xml` 中 添加`<beans:import resource="rabbitmq.xml"/>` (1)用户要先在 `applicationContext-rabbitmq.xml` 中 添加`<beans:import resource="rabbitmq.xml"/>`
(2)然后在`config.properties`中自行添加mq相关的配置如图: (2)然后在`config.properties`中自行添加mq相关的配置如图:
![](/assets/config.png) ![](/assets/config.png)
## 2 自定义消息 ## 2 自定义消息
在融租易的接口管理的消息队列模块定义功中,提供了自定义队列和交换机的功能,可以根据配置实现自定义的队列和交换机的匹配,避免了在xml中的繁琐配置。 在融租易的接口管理的消息队列模块定义功中,提供了自定义队列和交换机的功能,可以根据配置实现自定义的队列和交换机的匹配,避免了在xml中的繁琐配置。
### 2.1 定义交换机 ### 2.1 定义交换机
在接口管理的消息队列定义模块中,首先进行交换机的定义,界面如图:![](/assets/exchange.png)注意: 在接口管理的消息队列定义模块中,首先进行交换机的定义,界面如图:![](/assets/exchange.png)注意:
交换机名称不可以重复定义,一个交换机可以对应多个队列。 交换机名称不可以重复定义,一个交换机可以对应多个队列。
交换机类型可以分为**direct\(唯一模式\),topic\(主题模式\),fanout\(广播模式\),**rabbitmq会根据你的交换机类型和定义的路由键,完成消息的转发**。** 交换机类型可以分为**direct\(唯一模式\),topic\(主题模式\),fanout\(广播模式\),**rabbitmq会根据你的交换机类型和定义的路由键,完成消息的转发**。**
持久化(durable):交换机在服务关闭后,清除与否。 持久化(durable):交换机在服务关闭后,清除与否。
自动删除(auto-delete):当交换机没有队列绑定时,删除与否。 自动删除(auto-delete):当交换机没有队列绑定时,删除与否。
### 2.2 定义队列和路由键 ### 2.2 定义队列和路由键
定义完交换机后,你还需要定义你的队列和相应的匹配模式(既路由键)界面如图:![](/assets/queue.png)注意: 定义完交换机后,你还需要定义你的队列和相应的匹配模式(既路由键)界面如图:![](/assets/queue.png)注意:
队列名称不可以重复,但是一个队列可以对应多个交换机。 队列名称不可以重复,但是一个队列可以对应多个交换机。
匹配模式(routing-key):此处的匹配模式即为路由键 匹配模式(routing-key):此处的匹配模式即为路由键
持久化(durable):队列在服务关闭后,清除与否。 持久化(durable):队列在服务关闭后,清除与否。
自动删除(auto-delete):当队列没有绑定交换机时,删除与否。 自动删除(auto-delete):当队列没有绑定交换机时,删除与否。
私有队列(exclusive): 仅创建者可以使用的私有队列,断开后自动删除. 私有队列(exclusive): 仅创建者可以使用的私有队列,断开后自动删除.
## 3 发送消息/接收消息 ## 3 发送消息/接收消息
在融租易中,消息的发送和接收都提供了可以直接调用的接口 在融租易中,消息的发送和接收都提供了可以直接调用的接口
### 3.1 消息的匹配规则 ### 3.1 消息的匹配规则
1.唯一模式\(**Direct Exchange**\): 1.唯一模式\(**Direct Exchange**\):
处理路由键。需要将一个队列绑定到交换机上,要求该消息与一个特定的路由键完全匹配。这是一个完整的匹配。如果一个队列绑定到该交换机上要求路由键 “hls”,则只有被标记为“hls”的消息才被转发,不会转发hls.sys,也不会转发hls.fnd,只会转发hls。 处理路由键。需要将一个队列绑定到交换机上,要求该消息与一个特定的路由键完全匹配。这是一个完整的匹配。如果一个队列绑定到该交换机上要求路由键 “hls”,则只有被标记为“hls”的消息才被转发,不会转发hls.sys,也不会转发hls.fnd,只会转发hls。
2.广播模式\(**Fanout Exchange**\): 2.广播模式\(**Fanout Exchange**\):
不处理路由键。你只需要简单的将队列绑定到交换机上。一个发送到交换机的消息都会被转发到与该交换机绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。Fanout交换机转发消息是最快的。 不处理路由键。你只需要简单的将队列绑定到交换机上。一个发送到交换机的消息都会被转发到与该交换机绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。Fanout交换机转发消息是最快的。
3.主题模式\(**Topic Exchange**\): 3.主题模式\(**Topic Exchange**\):
将路由键和某模式进行匹配。此时队列需要绑定要一个模式上。符号“\#”匹配一个或多个词,符号“\*”匹配不多不少一个词。因此“hls.\#”能够匹配到“hls.sys.adaptor”,但是“hls.\*” 只会匹配到“hls.sys”。 将路由键和某模式进行匹配。此时队列需要绑定要一个模式上。符号“\#”匹配一个或多个词,符号“\*”匹配不多不少一个词。因此“hls.\#”能够匹配到“hls.sys.adaptor”,但是“hls.\*” 只会匹配到“hls.sys”。
### 3.2 发送消息 ### 3.2 发送消息
融租易中,通过调用`IRabbitProducerService`接口中的`sendMessage(JSONObject json)`方法,来实现消息的发送 融租易中,通过调用`IRabbitProducerService`接口中的`sendMessage(JSONObject json)`方法,来实现消息的发送
注意: 注意:
接口调用时必须使用spring中的注入方式。 接口调用时必须使用spring中的注入方式。
json必须按照格式如: json必须按照格式如:
```json ```json
{ {
"header":{ "header":{
"orgCode":"hls", "orgCode":"hls",
"apiName":"demoMQ", "apiName":"demoMQ",
"sysCode":"fnd", "sysCode":"fnd",
"routingKey":"hls", "routingKey":"hls",
"respCode":"sys", "respCode":"sys",
"respMsg":"recived", "respMsg":"recived",
"transDate":"Thu Jul 27 2017 10:47:17 GMT+0800 (CST)", "transDate":"Thu Jul 27 2017 10:47:17 GMT+0800 (CST)",
"transNo":"123" "transNo":"123"
}, },
"busiData":{ "busiData":{
"contractId":1 "contractId":1
}, },
"securityInfo":{ "securityInfo":{
"userName":"hd001", "userName":"hd001",
"userPassword":"123456" "userPassword":"123456"
} }
} }
``` ```
### 3.3 接收消息 ### 3.3 接收消息
融租易中对消息的接收进行了统一处理,只需要单独编写业务的实现类即可(此处必须实现IRabbitMessageConsumerService接口),消息会先被内部的消息监听接口统一处理,然后再根据消息中的apiName找到对应的接口实现类,调用其中的process方法。 融租易中对消息的接收进行了统一处理,只需要单独编写业务的实现类即可(此处必须实现IRabbitMessageConsumerService接口),消息会先被内部的消息监听接口统一处理,然后再根据消息中的apiName找到对应的接口实现类,调用其中的process方法。
示例代码: 示例代码:
```java ```java
@Service @Service
public class ConsumerDemoServiceImpl implements IRabbitMessageConsumerService { public class ConsumerDemoServiceImpl implements IRabbitMessageConsumerService {
@Autowired(required = false) @Autowired(required = false)
private IRabbitProducerService iRabbitProducerService;//消息发送类 private IRabbitProducerService iRabbitProducerService;//消息发送类
@Autowired @Autowired
private IXXXService service;//业务类 private IXXXService service;//业务类
@Override @Override
public String getApiName() { public String getApiName() {
return "demoMQ"; return "demoMQ";
} }
@Override @Override
public void process(JSONObject json) { public void process(JSONObject json) {
//业务处理 //业务处理
service.doSomething(json) service.doSomething(json)
//返回消息 //返回消息
ResponseInfo responseInfo = null; ResponseInfo responseInfo = null;
try { try {
responseInfo = RabbitMQUtils.getResposeInfo(json); responseInfo = RabbitMQUtils.getResposeInfo(json);
} catch (ParseException e) { } catch (ParseException e) {
e.printStackTrace(); e.printStackTrace();
} }
JSONObject respose= new JSONObject().fromObject(responseInfo); JSONObject respose= new JSONObject().fromObject(responseInfo);
iRabbitProducerService.sendMessage(respose); iRabbitProducerService.sendMessage(respose);
} }
} }
``` ```
### ###
# 工作流功能说明 # 工作流功能说明
### 1.流程设计 ### 1.流程设计
可以新建一个流程或者直接导入BPMN定义文件 可以新建一个流程或者直接导入BPMN定义文件
融租易的环境有一些样例,可以下载学习。 融租易的环境有一些样例,可以下载学习。
以下截图以`请假流程`为例 以下截图以`请假流程`为例
使用activiti editor设计流程 使用activiti editor设计流程
![](/assets/act_vacationReq_ed.png) ![](/assets/act_vacationReq_ed.png)
如图点击编辑按钮会弹出流程设计器页面 如图点击编辑按钮会弹出流程设计器页面
![](/assets/act_vacationReq_exp.png) ![](/assets/act_vacationReq_exp.png)
> 左边有各种节点,事件等,可以拖拽到中间,右边是当前节点的属性烂 > 左边有各种节点,事件等,可以拖拽到中间,右边是当前节点的属性烂
关于常用节点,以及常用属性,参考[流程设计器简易教程](activiti_editor_helper.md) 关于常用节点,以及常用属性,参考[流程设计器简易教程](activiti_editor_helper.md)
### 2.流程部署 ### 2.流程部署
流程设计好以后,点击流程设计页面操作栏的最后一个按钮(勾)即可发布流程 流程设计好以后,点击流程设计页面操作栏的最后一个按钮(勾)即可发布流程
> 未发布过的流程或者改动过的流程,勾会以绿色展示,反之灰色 > 未发布过的流程或者改动过的流程,勾会以绿色展示,反之灰色
发布以后可以在流程部署页面查看部署情况 发布以后可以在流程部署页面查看部署情况
> 如果有旧的流程启动了,发布的新版不会影响旧流程的运行 > 如果有旧的流程启动了,发布的新版不会影响旧流程的运行
### 3.流程启动 ### 3.流程启动
实际项目中需要客户化开发流程启动页面,参考`工作流测试`页面 实际项目中需要客户化开发流程启动页面,参考`工作流测试`页面
工作流测试页面可以动态解析`表单属性`,请假流程的启动页面如图所示 工作流测试页面可以动态解析`表单属性`,请假流程的启动页面如图所示
![](/assets/act_vacationReq_exp_start.png) ![](/assets/act_vacationReq_exp_start.png)
### 4.待办事项与历史流程 ### 4.待办事项与历史流程
可以在`我的待办`中查看当前登录用户(根据员工号)需要处理的待办事项 可以在`我的待办`中查看当前登录用户(根据员工号)需要处理的待办事项
> 管理员可以在`待办事项中(管理员)`查看所有人的待办 > 管理员可以在`待办事项中(管理员)`查看所有人的待办
在待办页面点击办理,即可处理相应事项。 在待办页面点击办理,即可处理相应事项。
![](/assets/act_vacationReq_exp_deal.png) ![](/assets/act_vacationReq_exp_deal.png)
右上角的`审批动作`可以在通过在流程设计器里设置当前`人工任务`的表单属性动态生成,默认为 右上角的`审批动作`可以在通过在流程设计器里设置当前`人工任务`的表单属性动态生成,默认为
* 同意 * 同意
* 拒绝 * 拒绝
* 转交 * 转交
> 对于activiti,审批动作仅仅是传回后台的值不一样 > 对于activiti,审批动作仅仅是传回后台的值不一样
这里的表单信息也需要需要客户化开发的,其实是一个iframe嵌套的页面,会在待办页面自动解析 这里的表单信息也需要需要客户化开发的,其实是一个iframe嵌套的页面,会在待办页面自动解析
> 页面路径在流程设计器里指定当前任务的`表单的标识Key` > 页面路径在流程设计器里指定当前任务的`表单的标识Key`
demo放在`view/activiti/include` demo放在`view/activiti/include`
`历史流程`页面可以查看已经结束和正在运行的流程信息 `历史流程`页面可以查看已经结束和正在运行的流程信息
## 审批链 ## 审批链
--- ---
功能入口是在 `流程设计` 界面。 功能入口是在 `流程设计` 界面。
<img width='460' src='/assets/approve_chain_entry.png'/> <img width='460' src='/assets/approve_chain_entry.png'/>
点击 编辑图标 进入该流程的审批链配置界面 点击 编辑图标 进入该流程的审批链配置界面
<img width='900' src='/assets/approve_chain@2x.png'/> <img width='900' src='/assets/approve_chain@2x.png'/>
在这个编辑页面中,所有的 `UserTask` 节点会以 标签页的形式自动列出来。 在这个编辑页面中,所有的 `UserTask` 节点会以 标签页的形式自动列出来。
切换标签页,下方表格中的数据会自动刷新为对应节点的数据。 切换标签页,下方表格中的数据会自动刷新为对应节点的数据。
上方的 操作按钮 是针对每个 标签页 操作的,并非针对整个页面。 上方的 操作按钮 是针对每个 标签页 操作的,并非针对整个页面。
主要属性说明: 主要属性说明:
* 名称 * 名称
自定义,任意。`不允许重复` 自定义,任意。`不允许重复`。
节点原始名称 和 审批链的名称 拼接起来 作为运行时的名称。 节点原始名称 和 审批链的名称 拼接起来 作为运行时的名称。
* 审批人 * 审批人
内置 3 种 内置 3 种
* 申请人(自己) * 申请人(自己)
* 上级(申请人直接领导) * 上级(申请人直接领导)
* 申请人部门领导 * 申请人部门领导
其他情况统统归为 `自定义表达式` 其他情况统统归为 `自定义表达式`
> 注意,这个功能要求流程的初始化器(Initiator)必须指定为 `initiator` > 注意,这个功能要求流程的初始化器(Initiator)必须指定为 `initiator`
* 审批岗位 * 审批岗位
支持选择系统中的`岗位`,也支持自定义的`表达式` 支持选择系统中的`岗位`,也支持自定义的`表达式`
> 目前在指定了审批人的情况下,再指定审批岗位是无效果的 > 目前在指定了审批人的情况下,再指定审批岗位是无效果的
* 表单 * 表单
允许该轮次的审批显示指定的单据明细页面。 许该轮次的审批显示指定的单据明细页面。
如果没有指定,则显示该节点原始设置的值。 果没有指定,则显示该节点原始设置的值。
* 顺序号 * 顺序号
用来控制审批的先后 (目前尚不支持多人同时在同一轮次审批) 来控制审批的先后 (目前尚不支持多人同时在同一轮次审批)
* 跳过条件 * 跳过条件
表达式,有值,且值为 true 时,当前轮次跳过。 达式,有值,且值为 true 时,当前轮次跳过。
默认留空,表示不跳过。 认留空,表示不跳过。
* 当前轮次因为 `跳过条件`被跳过时,是否直接结束审批链 * 当前轮次因为 `跳过条件`被跳过时,是否直接结束审批链
* 启用 * 启用
高优先级过滤条件(不启用肯定就没有作用) 优先级过滤条件(不启用肯定就没有作用)
* 描述 * 描述
无明确用途 明确用途
--- ---
在设计器中设计审批节点时,由审批链控制的节点,不需要在去指定 `任务派遣`,可以完全在审批链中定义。即使指定了,也会被审批链中的值覆盖! 在设计器中设计审批节点时,由审批链控制的节点,不需要在去指定 `任务派遣`,可以完全在审批链中定义。即使指定了,也会被审批链中的值覆盖!
除了与审批人、岗位有关的属性以外,其他属性的含义、作用保持不变。 除了与审批人、岗位有关的属性以外,其他属性的含义、作用保持不变。
`名称`稍微有变化:会和审批链的名称拼接在一起 `名称`稍微有变化:会和审批链的名称拼接在一起
> 请注意:目前审批链还不支持多例(会签) > 请注意:目前审批链还不支持多例(会签)
## 自定义任务超时时间和动态调整优先级 ## 自定义任务超时时间和动态调整优先级
--- ---
对于人工任务节点,在流程设计器中可以设置任务到期时间 对于人工任务节点,在流程设计器中可以设置任务到期时间
![](/assets/wfl_user_task_approve.png) ![](/assets/wfl_user_task_approve.png)
支持ISO8601 标准的日期格式,也可以写流程变量。如PT8H(ISO8601标准支持),表明该节点的任务的限定时间是8小时。 支持ISO8601 标准的日期格式,也可以写流程变量。如PT8H(ISO8601标准支持),表明该节点的任务的限定时间是8小时。
HAP 实现了一个简单任务,每30分钟执行一次,会动态调整设置了到期日期的任务的优先级。 HAP 实现了一个简单任务,每30分钟执行一次,会动态调整设置了到期日期的任务的优先级。
如何自定义超时时间的计算方式以及自定义优先级调整策略? 如何自定义超时时间的计算方式以及自定义优先级调整策略?
实现 `ICustomTaskProcessor` 即可: 实现 `ICustomTaskProcessor` 即可:
```java ```java
/** /**
* @param task 任务节点 * @param task 任务节点
* 动态设置task的优先级,调用getDueTime,获取任务剩余时间 * 动态设置task的优先级,调用getDueTime,获取任务剩余时间
* 建议不要改动其他属性,仅仅设置优先级 * 建议不要改动其他属性,仅仅设置优先级
* @return 一般情况返回参数task * @return 一般情况返回参数task
*/ */
Task processPriority(Task task); Task processPriority(Task task);
/** /**
* @param startDate task的创建时间 * @param startDate task的创建时间
* @param dueTime 当前task的任务的限定时间 * @param dueTime 当前task的任务的限定时间
* *
* 根据task的创建时间和限定时间,返回任务的剩余时间,单位秒 * 根据task的创建时间和限定时间,返回任务的剩余时间,单位秒
* *
* @return 返回经过计算后的任务剩余时间 ,负数表示超时时间 * @return 返回经过计算后的任务剩余时间 ,负数表示超时时间
*/ */
Long getDueTime(Date startDate,Long dueTime); Long getDueTime(Date startDate,Long dueTime);
/** /**
* 是否继续处理,如果返回false,不会再继续执行其他实现类 * 是否继续处理,如果返回false,不会再继续执行其他实现类
* */ * */
boolean processorContinue(); boolean processorContinue();
int getOrder(); int getOrder();
``` ```
参考 HAP 提供的一个默认实现 `DefaultCustomTaskProcessor` 参考 HAP 提供的一个默认实现 `DefaultCustomTaskProcessor`
超时时间没做特殊处理,如果剩余时间不足三分之二将优先级上调一级,如果不足三分之一把优先级设置为高。超时则设置为最高(100) 超时时间没做特殊处理,如果剩余时间不足三分之二将优先级上调一级,如果不足三分之一把优先级设置为高。超时则设置为最高(100)
1.首先实现 `getDueTime` 方法,该方法应该返回实际意义上,当前任务剩余的时间。 1.首先实现 `getDueTime` 方法,该方法应该返回实际意义上,当前任务剩余的时间。
> 比如我们设置了PT8H,但是我们想让这个任务的时效是8个工时,则可以在这里写好处理逻辑 > 比如我们设置了PT8H,但是我们想让这个任务的时效是8个工时,则可以在这里写好处理逻辑
> 参数 `dueTime` 是一个 Long 类型的参数,它的值是设置的超时时间(比如8小时,任务的dueDate与startDate的时间差) > 参数 `dueTime` 是一个 Long 类型的参数,它的值是设置的超时时间(比如8小时,任务的dueDate与startDate的时间差)
2.`processPriority` 方法则可以自定义优先级的调整规则,这里应该调用`getDueTime` 拿到任务的剩余时间或者超时时间,通过设置一定的规则,调整优先级(`task.setPriority(xx)`) 2.`processPriority` 方法则可以自定义优先级的调整规则,这里应该调用`getDueTime` 拿到任务的剩余时间或者超时时间,通过设置一定的规则,调整优先级(`task.setPriority(xx)`)
3.`getOrder` 方法用于实现类排序,数值越小,执行该实现类的优先级越高,框架的默认实现类order = 999,想只执行自己的实现类,设置一个较小的数值,重写 `processorContinue` 方法,并返回false 即可 3.`getOrder` 方法用于实现类排序,数值越小,执行该实现类的优先级越高,框架的默认实现类order = 999,想只执行自己的实现类,设置一个较小的数值,重写 `processorContinue` 方法,并返回false 即可
工作流开发指南 工作流开发指南
--- ---
## UserTask ## UserTask
--- ---
### 指定审批人、组 ### 指定审批人、组
在流程设计器中,选定`人工任务`节点,右边属性栏,点击`任务派遣` 弹出对话框 在流程设计器中,选定`人工任务`节点,右边属性栏,点击`任务派遣` 弹出对话框
<img width='600' src='/assets/usertask-assignment.png'/> <img width='600' src='/assets/usertask-assignment.png'/>
其中: 其中:
* 指派对象 * 指派对象
只能有一个人,可以点击`选择`按钮来从系统中选择员工 只能有一个人,可以点击`选择`按钮来从系统中选择员工
* 候选用户 * 候选用户
可以添加多个,每个指定一个`员工工号` 可以添加多个,每个指定一个`员工工号`
* 候选组 * 候选组
可以添加多个,每个指定一个`岗位代码` 可以添加多个,每个指定一个`岗位代码`
### 动态审批人、组 ### 动态审批人、组
与 上面类似,不过动态的审批人和组不是选择的,而是通过表达式计算得来。 与 上面类似,不过动态的审批人和组不是选择的,而是通过表达式计算得来。
表达式有两种情况 表达式有两种情况
* 引用变量 * 引用变量
比如`${initiator}`, 其中`initiator` 就是一个变量 比如`${initiator}`, 其中`initiator` 就是一个变量
* 调用 service 方法 * 调用 service 方法
比如`${orgStructure.getDirector(initiator)}`,其中 `orgStructure` 是一个自定的用于工作流的 `bean`,参数则可以使用任意的变量、常量等 比如`${orgStructure.getDirector(initiator)}`,其中 `orgStructure` 是一个自定的用于工作流的 `bean`,参数则可以使用任意的变量、常量等
> `execution` 是一个固有的、特殊的变量,指代 `org.activiti.engine.delegate.DelegateExecution`,非常有用 > `execution` 是一个固有的、特殊的变量,指代 `org.activiti.engine.delegate.DelegateExecution`,非常有用
关于自定义在工作流中用的 `bean` 关于自定义在工作流中用的 `bean`
HAP 提供一个 接口 `com.hand.hap.activiti.custom.IActivitiBean` HAP 提供一个 接口 `com.hand.hap.activiti.custom.IActivitiBean`
实现这个接口并被定义注册为 bean,其中的任何 `public` 方法都可以在工作流表达式中直接调用。 实现这个接口并被定义注册为 bean,其中的任何 `public` 方法都可以在工作流表达式中直接调用。
默认 bean 的 名字就是引用名,也可以覆盖接口的默认实现,指定名称。 默认 bean 的 名字就是引用名,也可以覆盖接口的默认实现,指定名称。
### 审批动作 ### 审批动作
Hap 的工作流在审批的时候默认有两个标准的动作: Hap 的工作流在审批的时候默认有两个标准的动作:
* 同意(APPROVED) * 同意(APPROVED)
* 拒绝(REJECTED) * 拒绝(REJECTED)
当审批者点击按钮以后,审批动作 id 会被保存到流程的共享变量区域。 当审批者点击按钮以后,审批动作 id 会被保存到流程的共享变量区域。
key 为 `approveResult`,这个变量可以直接在表达式中使用。 key 为 `approveResult`,这个变量可以直接在表达式中使用。
``` ```
${approveResult=='REJECTED'} ${approveResult=='REJECTED'}
``` ```
流程设计时,可以控制这两个按钮显示的文本;也可以控制只显示其中的一个。 流程设计时,可以控制这两个按钮显示的文本;也可以控制只显示其中的一个。
允许自定义额外的审批动作,自动动作的 id 同样会保存在 `approveResult` 变量中,通常需要流程显示处理(比如选择网关)。 允许自定义额外的审批动作,自动动作的 id 同样会保存在 `approveResult` 变量中,通常需要流程显示处理(比如选择网关)。
<img width='920' src='/assets/custom-approve-action.png'/> <img width='920' src='/assets/custom-approve-action.png'/>
### 自动结束流程(HAP 扩展特性) ### 自动结束流程(HAP 扩展特性)
当一个任务被拒绝时,绝大多数情况下,这个流程应该直接结束。 当一个任务被拒绝时,绝大多数情况下,这个流程应该直接结束。
但在工作流中,`审批拒绝``审批同意` 仅仅是给变量赋的参数值不同而已,如果希望流程结束,应该加一个 `选择网关`,判断审批结果是否为 `拒绝` ,然后引导流程走向`结束事件` 但在工作流中,`审批拒绝``审批同意` 仅仅是给变量赋的参数值不同而已,如果希望流程结束,应该加一个 `选择网关`,判断审批结果是否为 `拒绝` ,然后引导流程走向`结束事件`
显然,这很繁琐,尤其是当流程中`人工任务`节点比较多时。 显然,这很繁琐,尤其是当流程中`人工任务`节点比较多时。
HAP 做了一个优化: HAP 做了一个优化:
> 如果一个`人工任务` 的下一个节点仍然是 `任务(Task 类型)`,那么 HAP 将会自动插入一个`选择网关`和`终止事件` 来自动做结束流程的操作 > 如果一个`人工任务` 的下一个节点仍然是 `任务(Task 类型)`,那么 HAP 将会自动插入一个`选择网关`和`终止事件` 来自动做结束流程的操作
这个优化在流程的图上是看不出来的,用户完全无感知。 这个优化在流程的图上是看不出来的,用户完全无感知。
### 会签设置 ### 会签设置
自定义属性:`nrOfApproved``nrOfRejected` 自定义属性:`nrOfApproved``nrOfRejected`
集合 集合
变量 变量
任务派遣 任务派遣
完成条件 完成条件
## 网关 ## 网关
--- ---
选择网关 选择网关
> 选择网关`必须` 指定一个默认的连线,否则会出现不固定的行为。 > 选择网关`必须` 指定一个默认的连线,否则会出现不固定的行为。
并行网关 行网关
> 并行网关会在`所有连入`的连线都激活以后才会开始执行 > 并行网关会在`所有连入`的连线都激活以后才会开始执行
## ServiceTask ## ServiceTask
--- ---
## 待办通知 ## 待办通知
--- ---
## 审批链 ## 审批链
--- ---
审批链功能不是 Activiti 的标准功能,属于 HAP 开发的外挂性质的扩展。 审批链功能不是 Activiti 的标准功能,属于 HAP 开发的外挂性质的扩展。
功能入口是在 `流程设计` 界面。 功能入口是在 `流程设计` 界面。
<img width='460' src='/assets/approve_chain_entry.png'/> <img width='460' src='/assets/approve_chain_entry.png'/>
点击 编辑图标 进入该流程的审批链配置界面 点击 编辑图标 进入该流程的审批链配置界面
<img width='900' src='/assets/approve_chain@2x.png'/> <img width='900' src='/assets/approve_chain@2x.png'/>
在这个编辑页面中,所有的 `UserTask` 节点会以 标签页的形式自动列出来。 在这个编辑页面中,所有的 `UserTask` 节点会以 标签页的形式自动列出来。
切换标签页,下方表格中的数据会自动刷新为对应节点的数据。 切换标签页,下方表格中的数据会自动刷新为对应节点的数据。
上方的 操作按钮 是针对每个 标签页 操作的,并非针对整个页面。 上方的 操作按钮 是针对每个 标签页 操作的,并非针对整个页面。
主要属性说明: 主要属性说明:
* 名称 * 名称
自定义,任意。`不允许重复` 自定义,任意。`不允许重复`
节点原始名称 和 审批链的名称 拼接起来 作为运行时的名称。 节点原始名称 和 审批链的名称 拼接起来 作为运行时的名称。
* 审批人 * 审批人
内置 3 种 内置 3 种
* 申请人(自己) * 申请人(自己)
* 上级(申请人直接领导) * 上级(申请人直接领导)
* 申请人部门领导 * 申请人部门领导
其他情况统统归为 `自定义表达式` 其他情况统统归为 `自定义表达式`
> 注意,这个功能要求流程的初始化器(Initiator)必须指定为 `initiator` > 注意,这个功能要求流程的初始化器(Initiator)必须指定为 `initiator`
* 审批岗位 * 审批岗位
支持选择系统中的`岗位`,也支持自定义的`表达式` 支持选择系统中的`岗位`,也支持自定义的`表达式`
> 目前在指定了审批人的情况下,再指定审批岗位是无效果的 > 目前在指定了审批人的情况下,再指定审批岗位是无效果的
* 表单 * 表单
允许该轮次的审批显示指定的单据明细页面。 允许该轮次的审批显示指定的单据明细页面。
如果没有指定,则显示该节点原始设置的值。 如果没有指定,则显示该节点原始设置的值。
* 顺序号 * 顺序号
用来控制审批的先后 (目前尚不支持多人同时在同一轮次审批) 用来控制审批的先后 (目前尚不支持多人同时在同一轮次审批)
* 跳过条件 * 跳过条件
表达式,有值,且值为 true 时,当前轮次跳过。 表达式,有值,且值为 true 时,当前轮次跳过。
默认留空,表示不跳过。 默认留空,表示不跳过。
* 当前轮次因为 `跳过条件`被跳过时,是否直接结束审批链 * 当前轮次因为 `跳过条件`被跳过时,是否直接结束审批链
* 启用 * 启用
高优先级过滤条件(不启用肯定就没有作用) 高优先级过滤条件(不启用肯定就没有作用)
* 描述 * 描述
无明确用途 无明确用途
--- ---
在设计器中设计审批节点时,由审批链控制的节点,不需要在去指定 `任务派遣`,可以完全在审批链中定义。即使指定了,也会被审批链中的值覆盖! 在设计器中设计审批节点时,由审批链控制的节点,不需要在去指定 `任务派遣`,可以完全在审批链中定义。即使指定了,也会被审批链中的值覆盖!
除了与审批人、岗位有关的属性以外,其他属性的含义、作用保持不变。 除了与审批人、岗位有关的属性以外,其他属性的含义、作用保持不变。
`名称`稍微有变化:会和审批链的名称拼接在一起 `名称`稍微有变化:会和审批链的名称拼接在一起
> 请注意:目前审批链还不支持多例(会签) > 请注意:目前审批链还不支持多例(会签)
# 工作流demo # 工作流demo
整体流程 整体流程
* 打开工作流模块,设计工作流流程 * 打开工作流模块,设计工作流流程
* 实现一个当前功能启动工作流的接口 * 实现一个当前功能启动工作流的接口
* 在自己的业务层调用自己实现的工作流类 * 在自己的业务层调用自己实现的工作流类
* 启动工作流 * 启动工作流
1 新建工作流并填写对应的信息 1 新建工作流并填写对应的信息
![](/assets/wfl_demo_1.png) ![](/assets/wfl_demo_1.png)
![](/assets/wfl_demo_2.png) ![](/assets/wfl_demo_2.png)
2 编辑对应的审批节点链,可参考已实现的工作流程 2 编辑对应的审批节点链,可参考已实现的工作流程
![](/assets/wfl_demo_3.png) ![](/assets/wfl_demo_3.png)
3 编写一个自己的实现类,实现IActivitiCommonService接口 3 编写一个自己的实现类,实现IActivitiCommonService接口
![](/assets/wfl_demo_4.png) ![](/assets/wfl_demo_4.png)
实现类代码如下 实现类代码如下
```java ```java
package hls.core.wfl.service.impl; package hls.core.wfl.service.impl;
import com.hand.hap.activiti.dto.ReProcdef; import com.hand.hap.activiti.dto.ReProcdef;
import com.hand.hap.activiti.service.IActivitiService; import com.hand.hap.activiti.service.IActivitiService;
import com.hand.hap.activiti.service.IReProcdefService; import com.hand.hap.activiti.service.IReProcdefService;
import com.hand.hap.core.IRequest; import com.hand.hap.core.IRequest;
import hls.core.prj.dto.PrjProject; import hls.core.prj.dto.PrjProject;
import hls.core.prj.dto.PrjProjectMeeting; import hls.core.prj.dto.PrjProjectMeeting;
import hls.core.prj.mapper.PrjProjectMeetingMapper; import hls.core.prj.mapper.PrjProjectMeetingMapper;
import hls.core.prj.service.PrjProjectService; import hls.core.prj.service.PrjProjectService;
import hls.core.wfl.service.IActivitiCommonService; import hls.core.wfl.service.IActivitiCommonService;
import net.sf.json.JSONObject; import net.sf.json.JSONObject;
import org.activiti.rest.service.api.engine.variable.RestVariable; import org.activiti.rest.service.api.engine.variable.RestVariable;
import org.activiti.rest.service.api.runtime.process.ProcessInstanceCreateRequest; import org.activiti.rest.service.api.runtime.process.ProcessInstanceCreateRequest;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;import java.util.List; import java.util.ArrayList;import java.util.List;
import java.util.Map; import java.util.Map;
@Service @Service
@Transactional @Transactional
public class PrjActivitStartServiceImpl implements IActivitiCommonService public class PrjActivitStartServiceImpl implements IActivitiCommonService
{ {
private static final String workFlowType = "PRJ_PROJECT"; @Autowired private static final String workFlowType = "PRJ_PROJECT"; @Autowired
private IActivitiService activitiService; private IActivitiService activitiService;
@Autowired @Autowired
private IReProcdefService reProcdefService; private IReProcdefService reProcdefService;
@Autowired @Autowired
private PrjProjectService projectService; private PrjProjectService projectService;
@Autowired @Autowired
private PrjProjectMeetingMapper meetingMapper; private PrjProjectMeetingMapper meetingMapper;
@Override @Override
public String getWorkFlowType() { return workFlowType; } public String getWorkFlowType() { return workFlowType; }
@Override @Override
public void process(IRequest iRequest, List list, Map params) public void process(IRequest iRequest, List list, Map params)
{ {
ProcessInstanceCreateRequest processInstanceCreateRequest = getProcessInstanceCreateRequest((PrjProject)list.get(0),iRequest); ProcessInstanceCreateRequest processInstanceCreateRequest = getProcessInstanceCreateRequest((PrjProject)list.get(0),iRequest);
activitiService.startProcess(iRequest, processInstanceCreateRequest); activitiService.startProcess(iRequest, processInstanceCreateRequest);
} }
private ProcessInstanceCreateRequest getProcessInstanceCreateRequest(PrjProject prjProject, IRequest iRequest) private ProcessInstanceCreateRequest getProcessInstanceCreateRequest(PrjProject prjProject, IRequest iRequest)
{ {
ProcessInstanceCreateRequest createRequest = new ProcessInstanceCreateRequest(); ProcessInstanceCreateRequest createRequest = new ProcessInstanceCreateRequest();
PrjProject project=projectService.selectByPrimaryKey(iRequest,prjProject); ReProcdef reProcdefs ; PrjProject project=projectService.selectByPrimaryKey(iRequest,prjProject); ReProcdef reProcdefs ;
reProcdefs = reProcdefService.queryReProcdef("PRJ_PROJECT_APPROVE","PRJ_PROJECT"); reProcdefs = reProcdefService.queryReProcdef("PRJ_PROJECT_APPROVE","PRJ_PROJECT");
String id = reProcdefs.getId_(); String id = reProcdefs.getId_();
String name = reProcdefs.getName_(); createRequest.setProcessDefinitionId(id); createRequest.setBusinessKey(prjProject.getProjectId().toString()); String name = reProcdefs.getName_(); createRequest.setProcessDefinitionId(id); createRequest.setBusinessKey(prjProject.getProjectId().toString());
List<RestVariable> variables = new ArrayList<RestVariable>(); List<RestVariable> transientVariables = new ArrayList<RestVariable>(); List<RestVariable> variables = new ArrayList<RestVariable>(); List<RestVariable> transientVariables = new ArrayList<RestVariable>();
RestVariable restVariable1 = new RestVariable(); RestVariable restVariable2 = new RestVariable(); RestVariable restVariable1 = new RestVariable(); RestVariable restVariable2 = new RestVariable();
RestVariable restVariable3 = new RestVariable(); RestVariable restVariable3 = new RestVariable();
RestVariable restVariable4 = new RestVariable(); RestVariable restVariable4 = new RestVariable();
RestVariable restVariable5 = new RestVariable(); RestVariable restVariable5 = new RestVariable();
RestVariable restVariable6 = new RestVariable(); RestVariable restVariable6 = new RestVariable();
restVariable1.setName("processDefinitionId"); restVariable1.setName("processDefinitionId");
restVariable1.setValue(id); variables.add(restVariable1); restVariable1.setValue(id); variables.add(restVariable1);
restVariable2.setName("prjProject"); restVariable2.setName("prjProject");
JSONObject jsonObject= new JSONObject().fromObject(prjProject); restVariable2.setValue(jsonObject.toString()); variables.add(restVariable2); JSONObject jsonObject= new JSONObject().fromObject(prjProject); restVariable2.setValue(jsonObject.toString()); variables.add(restVariable2);
restVariable3.setName("iRequest"); restVariable3.setValue(iRequest); restVariable3.setName("iRequest"); restVariable3.setValue(iRequest);
variables.add(restVariable3); variables.add(restVariable3);
restVariable4.setName("projectNum"); restVariable4.setValue(project.getProjectNumber()); variables.add(restVariable4); restVariable5.setName("projectName"); restVariable5.setValue(project.getProjectName()); variables.add(restVariable5); restVariable6.setName("startUserName"); restVariable6.setValue(iRequest.getEmployeeCode()); variables.add(restVariable6); restVariable4.setName("projectNum"); restVariable4.setValue(project.getProjectNumber()); variables.add(restVariable4); restVariable5.setName("projectName"); restVariable5.setValue(project.getProjectName()); variables.add(restVariable5); restVariable6.setName("startUserName"); restVariable6.setValue(iRequest.getEmployeeCode()); variables.add(restVariable6);
RestVariable restVariable7 = new RestVariable(); RestVariable restVariable7 = new RestVariable();
RestVariable restVariable8 = new RestVariable(); RestVariable restVariable9 = new RestVariable(); restVariable7.setName("documentCategory"); restVariable7.setValue(project.getDocumentCategory()); variables.add(restVariable7); restVariable8.setName("documentType"); restVariable8.setValue(project.getDocumentType()); RestVariable restVariable8 = new RestVariable(); RestVariable restVariable9 = new RestVariable(); restVariable7.setName("documentCategory"); restVariable7.setValue(project.getDocumentCategory()); variables.add(restVariable7); restVariable8.setName("documentType"); restVariable8.setValue(project.getDocumentType());
variables.add(restVariable8); restVariable9.setName("documentId"); restVariable9.setValue(project.getProjectId()); variables.add(restVariable8); restVariable9.setName("documentId"); restVariable9.setValue(project.getProjectId());
variables.add(restVariable9); variables.add(restVariable9);
RestVariable restVariable10 = new RestVariable(); RestVariable restVariable10 = new RestVariable();
restVariable10.setName("doubleFlag"); restVariable10.setName("doubleFlag");
PrjProjectMeeting meeting = new PrjProjectMeeting(); PrjProjectMeeting meeting = new PrjProjectMeeting();
meeting.setProjectId(prjProject.getProjectId()); meeting.setProjectId(prjProject.getProjectId());
List<PrjProjectMeeting> list = meetingMapper.select(meeting); if(list.size() >0) { restVariable10.setValue("Y"); }else{ restVariable10.setValue("N"); } List<PrjProjectMeeting> list = meetingMapper.select(meeting); if(list.size() >0) { restVariable10.setValue("Y"); }else{ restVariable10.setValue("N"); }
variables.add(restVariable10); variables.add(restVariable10);
RestVariable restVariable11 = new RestVariable(); restVariable11.setName("pName"); RestVariable restVariable11 = new RestVariable(); restVariable11.setName("pName");
restVariable11.setValue(name); restVariable11.setValue(name);
variables.add(restVariable11); createRequest.setVariables(variables); createRequest.setTransientVariables(transientVariables); variables.add(restVariable11); createRequest.setVariables(variables); createRequest.setTransientVariables(transientVariables);
return createRequest; return createRequest;
}} }}
``` ```
4 启动工作流 4 启动工作流
``` ```
Map<String,Object> params = new HashMap<String,Object>(); Map<String,Object> params = new HashMap<String,Object>();
params.put("workFlowType","PRJ_PROJECT"); params.put("workFlowType","PRJ_PROJECT");
activitiStartService.start(iRequest,projects,params); activitiStartService.start(iRequest,projects,params);
``` ```
\ No newline at end of file
流程设计器教程 流程设计器教程
--- ---
# 1.开始事件和结束事件 # 1.开始事件和结束事件
--- ---
对应着流程的开始和结束。 对应着流程的开始和结束。
> 应该保证每个分支都可以从开始走到结束 > 应该保证每个分支都可以从开始走到结束
## 开始事件 ## 开始事件
开始事件用来指明流程在哪里开始。开始事件的类型(流程在接收事件时启动, 还是在指定时间启动,等等),定义了流程如何启动。 开始事件用来指明流程在哪里开始。开始事件的类型(流程在接收事件时启动, 还是在指定时间启动,等等),定义了流程如何启动。
> 一般使用第一个空的开始事件就可以了,需要手动启动流程 > 一般使用第一个空的开始事件就可以了,需要手动启动流程
*属性* *属性*
![](/assets/act_vacationReq_bg_v.png) ![](/assets/act_vacationReq_bg_v.png)
* 初始化器 * 初始化器
当流程启动时,把当前登录的用户保存到哪个变量名中,可以在表达式里直接引用该变量 当流程启动时,把当前登录的用户保存到哪个变量名中,可以在表达式里直接引用该变量
* 表单属性 * 表单属性
当执行到开始事件时,所有的流程变量都是可用的,但是可能需要一些用于表单展示的自定义变量,此时可以在表单属性中定义,将会保存到流程的`共享变量`区域 当执行到开始事件时,所有的流程变量都是可用的,但是可能需要一些用于表单展示的自定义变量,此时可以在表单属性中定义,将会保存到流程的`共享变量`区域
![](/assets/act_vacationReq_form.png) ![](/assets/act_vacationReq_form.png)
如图是`请假流程`的开始事件的表单属性,在这里定义的变量,在`工作流测试页面`会自动渲染成表单 如图是`请假流程`的开始事件的表单属性,在这里定义的变量,在`工作流测试页面`会自动渲染成表单
支持以下的几种表单属性类型: 支持以下的几种表单属性类型:
* string (org.activiti.engine.impl.form.StringFormType) * string (org.activiti.engine.impl.form.StringFormType)
* long (org.activiti.engine.impl.form.LongFormType) * long (org.activiti.engine.impl.form.LongFormType)
* enum (org.activiti.engine.impl.form.EnumFormType) * enum (org.activiti.engine.impl.form.EnumFormType)
* date (org.activiti.engine.impl.form.DateFormType) * date (org.activiti.engine.impl.form.DateFormType)
* boolean (org.activiti.engine.impl.form.BooleanFormType) * boolean (org.activiti.engine.impl.form.BooleanFormType)
> 括号里是作为流程变量存储的对应JAVA类 > 括号里是作为流程变量存储的对应JAVA类
表达式和变量 表达式和变量
如果设置了`变量`,需要通过变量名来引用变量,表达式同理 如果设置了`变量`,需要通过变量名来引用变量,表达式同理
> 默认则是把id作为变量名使用 > 默认则是把id作为变量名使用
* 必须 如果勾选了,提交表单时没有提供该属性则抛出异常 * 必须 如果勾选了,提交表单时没有提供该属性则抛出异常
* 可读 不勾选则不会进行自动渲染显示,但是还是可以提交 * 可读 不勾选则不会进行自动渲染显示,但是还是可以提交
* 可写 不够勾选,还提交该属性,也会抛出异常 * 可写 不够勾选,还提交该属性,也会抛出异常
## 结束事件 ## 结束事件
结束事件表示(子)流程(分支)的结束。 结束事件都是触发事件。 这是说当流程达到结束事件,会触发一个结果。 结果的类型是通过事件的内部黑色图标表示的。 结束事件表示(子)流程(分支)的结束。 结束事件都是触发事件。 这是说当流程达到结束事件,会触发一个结果。 结果的类型是通过事件的内部黑色图标表示的。
> 一般使用一个空的结束事件就行了,意味着到达事件时不会指定抛出的结果。 这样,引擎会直接结束当前执行的分支,不会做其他事情。 > 一般使用一个空的结束事件就行了,意味着到达事件时不会指定抛出的结果。 这样,引擎会直接结束当前执行的分支,不会做其他事情。
### 自动结束流程(HAP 扩展特性) ### 自动结束流程(HAP 扩展特性)
当一个任务被拒绝时,绝大多数情况下,这个流程应该直接结束。 当一个任务被拒绝时,绝大多数情况下,这个流程应该直接结束。
但在工作流中,`审批拒绝``审批同意` 仅仅是给变量赋的参数值不同而已,如果希望流程结束,应该加一个 `选择网关`,判断审批结果是否为 `拒绝` ,然后引导流程走向`结束事件` 但在工作流中,`审批拒绝``审批同意` 仅仅是给变量赋的参数值不同而已,如果希望流程结束,应该加一个 `选择网关`,判断审批结果是否为 `拒绝` ,然后引导流程走向`结束事件`
显然,这很繁琐,尤其是当流程中`人工任务`节点比较多时。 显然,这很繁琐,尤其是当流程中`人工任务`节点比较多时。
HAP 做了一个优化: HAP 做了一个优化:
> 如果一个`人工任务` 的下一个节点仍然是 `任务(Task 类型)`,那么 HAP 将会自动插入一个`选择网关`和`终止事件` 来自动做结束流程的操作 > 如果一个`人工任务` 的下一个节点仍然是 `任务(Task 类型)`,那么 HAP 将会自动插入一个`选择网关`和`终止事件` 来自动做结束流程的操作
这个优化在流程的图上是看不出来的,用户完全无感知。 这个优化在流程的图上是看不出来的,用户完全无感知。
# 2.任务 # 2.任务
--- ---
常用的是人工任务,和服务任务 常用的是人工任务,和服务任务
## 人工任务(UserTask) ## 人工任务(UserTask)
![](/assets/act_vacationReq_userTask.png) ![](/assets/act_vacationReq_userTask.png)
### 任务派遣 ### 任务派遣
**指定审批人、组** **指定审批人、组**
点击`任务派遣` 弹出对话框 点击`任务派遣` 弹出对话框
<img width='600' src='/assets/usertask-assignment.png'/> <img width='600' src='/assets/usertask-assignment.png'/>
其中: 其中:
* 指派对象 * 指派对象
只能有一个人,可以点击`选择`按钮来从系统中选择员工 只能有一个人,可以点击`选择`按钮来从系统中选择员工
* 候选用户 * 候选用户
可以添加多个,每个指定一个`员工工号` 可以添加多个,每个指定一个`员工工号`
* 候选组 * 候选组
可以添加多个,每个指定一个`岗位代码` 可以添加多个,每个指定一个`岗位代码`
**动态审批人、组** **动态审批人、组**
与 上面类似,不过动态的审批人和组不是选择的,而是通过表达式计算得来。 与 上面类似,不过动态的审批人和组不是选择的,而是通过表达式计算得来。
`表达式`有两种情况 `表达式`有两种情况
* 引用变量 * 引用变量
比如`${initiator}`, 其中`initiator` 就是一个变量 比如`${initiator}`, 其中`initiator` 就是一个变量
* 调用 service 方法 * 调用 service 方法
比如`${orgStructure.getDirector(initiator)}`,其中 `orgStructure` 是一个自定的用于工作流的 `bean`,参数则可以使用任意的变量、常量等 比如`${orgStructure.getDirector(initiator)}`,其中 `orgStructure` 是一个自定的用于工作流的 `bean`,参数则可以使用任意的变量、常量等
> `execution` 是一个固有的、特殊的变量,指代 `org.activiti.engine.delegate.DelegateExecution`,非常有用 > `execution` 是一个固有的、特殊的变量,指代 `org.activiti.engine.delegate.DelegateExecution`,非常有用
关于自定义在工作流中用的 `bean` 关于自定义在工作流中用的 `bean`
HAP 提供一个 接口 `com.hand.hap.activiti.custom.IActivitiBean` HAP 提供一个 接口 `com.hand.hap.activiti.custom.IActivitiBean`
实现这个接口并被定义注册为 bean,其中的任何 `public` 方法都可以在工作流表达式中直接调用。 实现这个接口并被定义注册为 bean,其中的任何 `public` 方法都可以在工作流表达式中直接调用。
默认 bean 的 名字就是引用名,也可以覆盖接口的默认实现,指定名称。 默认 bean 的 名字就是引用名,也可以覆盖接口的默认实现,指定名称。
### 表单的标识Key ### 表单的标识Key
用于在待办明细页面中,动态渲染页面,设置为表单html页面的路径 用于在待办明细页面中,动态渲染页面,设置为表单html页面的路径
### 表单属性 ### 表单属性
跟开始事件的表单属性一样 跟开始事件的表单属性一样
**审批动作** **审批动作**
Hap 的工作流在审批的时候默认有两个标准的动作: Hap 的工作流在审批的时候默认有两个标准的动作:
* 同意(APPROVED) * 同意(APPROVED)
* 拒绝(REJECTED) * 拒绝(REJECTED)
当审批者点击按钮以后,审批动作 id 会被保存到流程的`共享变量`区域。 当审批者点击按钮以后,审批动作 id 会被保存到流程的`共享变量`区域。
key 为 `approveResult`,这个变量可以直接在表达式中使用。 key 为 `approveResult`,这个变量可以直接在表达式中使用。
``` ```
${approveResult=='REJECTED'} ${approveResult=='REJECTED'}
``` ```
流程设计时,可以控制这两个按钮显示的文本;也可以控制只显示其中的一个。 流程设计时,可以控制这两个按钮显示的文本;也可以控制只显示其中的一个。
允许自定义额外的审批动作,自动动作的 id 同样会保存在 `approveResult` 变量中,通常需要流程显示处理(比如选择网关)。 允许自定义额外的审批动作,自动动作的 id 同样会保存在 `approveResult` 变量中,通常需要流程显示处理(比如选择网关)。
<img width='920' src='/assets/custom-approve-action.png'/> <img width='920' src='/assets/custom-approve-action.png'/>
### 会签设置 ### 会签设置
会签通过设置`多实例`属性来实现,有串行和并行两种模式。 会签通过设置`多实例`属性来实现,有串行和并行两种模式。
> 三条竖线表示实例会并行执行。 三条横线表示顺序执行。 > 三条竖线表示实例会并行执行。 三条横线表示顺序执行。
<img width='920' src='/assets/activiti_parallel.png'/> <img width='920' src='/assets/activiti_parallel.png'/>
`集合` 应该返回一个字符串数组,表示用户集合 `集合` 应该返回一个字符串数组,表示用户集合
> 并行执行 会立刻为所有集合成员创建任务实例 > 并行执行 会立刻为所有集合成员创建任务实例
> 串行执行 会依次创建任务实例,完成一个才会创建下一个 > 串行执行 会依次创建任务实例,完成一个才会创建下一个
`元素变量` 遍历用户集合的单个对象变量名称 `元素变量` 遍历用户集合的单个对象变量名称
> 任务派遣 设置${元素变量}即可 > 任务派遣 设置${元素变量}即可
`完成条件` `完成条件`
> activiti 标准会签,默认有 3 个参数, 可以作为结束条件考量依据. > activiti 标准会签,默认有 3 个参数, 可以作为结束条件考量依据.
分别是 分别是
* nrOfInstances(实例个数,根据集集合表达式中获取的对象个数确定) * nrOfInstances(实例个数,根据集集合表达式中获取的对象个数确定)
* nrOfCompletedInstances(已经完成的实例个数,比如已经同意或者拒绝的) * nrOfCompletedInstances(已经完成的实例个数,比如已经同意或者拒绝的)
* nrOfActiveInstances(还未完成的实例个数) * nrOfActiveInstances(还未完成的实例个数)
> >
hap 自定义了两个: hap 自定义了两个:
* nrOfApproved(同意的实例个数) * nrOfApproved(同意的实例个数)
* nrOfRejected(拒绝的实例个数) * nrOfRejected(拒绝的实例个数)
## 服务任务(ServiceTask) ## 服务任务(ServiceTask)
用来调用外部java类 用来调用外部java类
有三种方式 有三种方式
### 类名 ### 类名
`类名`属性设置类的完整限定名 `类名`属性设置类的完整限定名
比如`com.hand.hap.activiti.demo.DemoServiceTask` 比如`com.hand.hap.activiti.demo.DemoServiceTask`
> 需要实现JavaDelegate接口 > 需要实现JavaDelegate接口
类只会初始化一次, 类似 servlet 的模式。 类只会初始化一次, 类似 servlet 的模式。
### 代理表达式 ### 代理表达式
执行解析代理对象的表达式,直接通过表达式确定代理对象 执行解析代理对象的表达式,直接通过表达式确定代理对象
比如`$(demoServiceTaskDelegate)` 比如`$(demoServiceTaskDelegate)`
> 参考com.hand.hap.activiti.demo.components.DemoServiceTaskDelegate 实现JavaDelegate接口和IActivitiBean > 参考com.hand.hap.activiti.demo.components.DemoServiceTaskDelegate 实现JavaDelegate接口和IActivitiBean
注意是用$符号,这是delegateExpression,与普通的表达式有区别 注意是用$符号,这是delegateExpression,与普通的表达式有区别
### 表达式 ### 表达式
可以调用指定方法 可以调用指定方法
`#{demoServiceTaskDelegate.method1(execution,task2Output)}` `#{demoServiceTaskDelegate.method1(execution,task2Output)}`
# 3.网关 # 3.网关
网关用来控制流程的流向 网关用来控制流程的流向
网关显示成菱形图形,内部有有一个小图标。 图标表示网关的类型 网关显示成菱形图形,内部有有一个小图标。 图标表示网关的类型
### 单一网关 ### 单一网关
内部图标是一个“X” 内部图标是一个“X”
当流程执行到这个网关,所有外出顺序流都会被处理一遍。 通过设置`跳转条件`控制流程走向。 当流程执行到这个网关,所有外出顺序流都会被处理一遍。 通过设置`跳转条件`控制流程走向。
> 比如 ${approveResult == 'REJECTED'},表示拒绝后的流程走向。 > 比如 ${approveResult == 'REJECTED'},表示拒绝后的流程走向。
可以设置`默认跳转`,当其他所以顺序流的跳转条件都返回false时触发。 可以设置`默认跳转`,当其他所以顺序流的跳转条件都返回false时触发。
> 注意如果多个顺序流的条件结果为true,单一网关只会选择一个执行。 > 注意如果多个顺序流的条件结果为true,单一网关只会选择一个执行。
### 并行网关 ### 并行网关
内部是一个“+”图标 内部是一个“+”图标
并行网关的功能是基于进入和外出的顺序流的: 并行网关的功能是基于进入和外出的顺序流的:
* 分支: 并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。 * 分支: 并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。
* 汇聚: 所有到达并行网关,在此等待的进入分支, 直到所有进入顺序流的分支都到达以后, 流程就会通过汇聚网关。 * 汇聚: 所有到达并行网关,在此等待的进入分支, 直到所有进入顺序流的分支都到达以后, 流程就会通过汇聚网关。
注意,如果同一个并行网关有多个进入和多个外出顺序流, 它就同时具有分支和汇聚功能。 这时,网关会先汇聚所有进入的顺序流,然后再切分成多个并行分支。 注意,如果同一个并行网关有多个进入和多个外出顺序流, 它就同时具有分支和汇聚功能。 这时,网关会先汇聚所有进入的顺序流,然后再切分成多个并行分支。
与其他网关的主要区别是,并行网关*不会解析条件*。 即使顺序流中定义了条件,也会被忽略。 与其他网关的主要区别是,并行网关*不会解析条件*。 即使顺序流中定义了条件,也会被忽略。
>参考[Activiti5.16中文手册](http://www.mossle.com/docs/activiti/index.html) >参考[Activiti5.16中文手册](http://www.mossle.com/docs/activiti/index.html)
\ No newline at end of file
# 部署 # 部署
# tomcat # tomcat
<a href="#tomcat"></a> <a href="#tomcat"></a>
### 1. 关闭 eclipse server `Auto Reload` 特性 ### 1. 关闭 eclipse server `Auto Reload` 特性
* 禁用默认值 * 禁用默认值
<img src="/assets/disable_autoreload_all.png" width="50%"/> <img src="/assets/disable_autoreload_all.png" width="50%"/>
* 单独禁用项 * 单独禁用项
<img src="/assets/disable_autoreload_1.png" width="50%"/> <img src="/assets/disable_autoreload_1.png" width="50%"/>
* 建议关闭 jar 扫描, 提高启动速度, 减少内存占用 , 修改 `catalina.properties ` * 建议关闭 jar 扫描, 提高启动速度, 减少内存占用 , 修改 `catalina.properties `
```properties ```properties
tomcat.util.scan.StandardJarScanFilter.jarsToSkip=*.jar tomcat.util.scan.StandardJarScanFilter.jarsToSkip=*.jar
``` ```
### 2. 内存设置 ### 2. 内存设置
* Linux 修改 `catalina.sh` ,开始处添加 * Linux 修改 `catalina.sh` ,开始处添加
```bash ```bash
JAVA_OPTS='-Xms512m -Xmx2048m' JAVA_OPTS='-Xms512m -Xmx2048m'
``` ```
* Windows 修改 `catalina.bat` * Windows 修改 `catalina.bat`
```bash ```bash
set JAVA_OPTS=-Xms512m -Xmx2048m set JAVA_OPTS=-Xms512m -Xmx2048m
``` ```
### 3. 启用远程 JMX 连接 ### 3. 启用远程 JMX 连接
修改 catalina.sh , 搜索 `[ "$1" = "start" ]` , 在下方添加 修改 catalina.sh , 搜索 `[ "$1" = "start" ]` , 在下方添加
```bash ```bash
JAVA_OPTS="-Dcom.sun.management.jmxremote \ JAVA_OPTS="-Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.port=1099 \ -Dcom.sun.management.jmxremote.port=1099 \
-Djava.rmi.server.hostname=192.168.1.111 \ -Djava.rmi.server.hostname=192.168.1.111 \
-Dcom.sun.management.jmxremote.ssl=false \ -Dcom.sun.management.jmxremote.ssl=false \
-Dcom.sun.management.jmxremote.authenticate=false $JAVA_OPTS" -Dcom.sun.management.jmxremote.authenticate=false $JAVA_OPTS"
``` ```
> linux 系统可以用 `` `hostname -i` `` 获取 ip > linux 系统可以用 `` `hostname -i` `` 获取 ip
# Weblogic # Weblogic
<a href="#weblogic"></a> <a href="#weblogic"></a>
weblogic 上的部署有些特殊,主要注意以下几点: weblogic 上的部署有些特殊,主要注意以下几点:
> **以下设置仅仅针对 weblogic 12 版本,其他版本可能存在通配符等问题** > **以下设置仅仅针对 weblogic 12 版本,其他版本可能存在通配符等问题**
### 1. JNDI 数据源 ### 1. JNDI 数据源
weblogic 上定义好数据源后,在 `config.properties` 中需要修改 jndi 的名字 weblogic 上定义好数据源后,在 `config.properties` 中需要修改 jndi 的名字
```properties ```properties
db.jndiName=hap_dev db.jndiName=hap_dev
``` ```
> tomcat 中是 java:comp/env/jdbc/hap_dev,这点不一样 > tomcat 中是 java:comp/env/jdbc/hap_dev,这点不一样
### 2. 新增 weblogic.xml ### 2. 新增 weblogic.xml
在 WEB-INF 目录下新增 weblogic.xml 在 WEB-INF 目录下新增 weblogic.xml
```bash ```bash
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<weblogic-web-app> <weblogic-web-app>
<container-descriptor> <container-descriptor>
<!--优先加载应用下的jar包,解决jar包冲突问题--> <!--优先加载应用下的jar包,解决jar包冲突问题-->
<!--具体jar包冲突问题,具体解决--> <!--具体jar包冲突问题,具体解决-->
<prefer-application-packages> <prefer-application-packages>
<package-name>org.springframework.*</package-name> <package-name>org.springframework.*</package-name>
<package-name>org.hibernate.*</package-name> <package-name>org.hibernate.*</package-name>
<package-name>javax.validation.*</package-name> <package-name>javax.validation.*</package-name>
<package-name>javax.validation.spi.*</package-name> <package-name>javax.validation.spi.*</package-name>
<package-name>org.slf4j.*</package-name> <package-name>org.slf4j.*</package-name>
<package-name>com.fasterxml.*</package-name> <package-name>com.fasterxml.*</package-name>
</prefer-application-packages> </prefer-application-packages>
<show-archived-real-path-enabled>true</show-archived-real-path-enabled> <show-archived-real-path-enabled>true</show-archived-real-path-enabled>
</container-descriptor> </container-descriptor>
</weblogic-web-app> </weblogic-web-app>
``` ```
### 3. weblogic部署异常解决方案 ### 3. weblogic部署异常解决方案
weblogic选择要部署的war包,若出现_wl_cls_gen.jar!这样的日志异常,可将war包,直接解压,进行部署。 weblogic选择要部署的war包,若出现_wl_cls_gen.jar!这样的日志异常,可将war包,直接解压,进行部署。
上述 3 点在部署的时候请留意。 上述 3 点在部署的时候请留意。
...@@ -156,11 +156,37 @@ public class demoJob extends AbstractJob{ ...@@ -156,11 +156,37 @@ public class demoJob extends AbstractJob{
* 下次执行时间:预计执行完本次任务后,根据指定的执行间隔推算的下一次执行时间点。 * 下次执行时间:预计执行完本次任务后,根据指定的执行间隔推算的下一次执行时间点。
* 实际执行时间:本次任务实际执行的时间点。 * 实际执行时间:本次任务实际执行的时间点。
### 5、后台创建计划任务 ### 5、后台创建周期任务
#### 5.1 后端直接创建计划任务 由于在某些业务模块中,可能需要直接在后端业务代码中定义计划任务,那么这个时候,融租易中提供了创建计划任务的接口可以供直接调用。
1.在某些业务模块,可能需要直接在后端业务代码中定义计划任务,那么这个时候,融租易中提供了创建计划任何的接口 #### 5.1 通过post请求直接创建周期任务
访问`"/hls/job/create"`地址,必须要为post请求类型,请求体中的参数也必须为json格式,如下:
```
{
"jobCreateDto": {
"jobClassName":"default",
"jobName": "jobname",
"jobGroup": "DEFAULT",
"triggerType": "CRON"
},
"taskId": "10001",
"scheduleName": "schname",
"defineStartTime": "14000000",
"defineEndTime": "140000000",
"businessParam": {
"contractId": "1"
}
}
```
json格式说明:
* jobCreateDto:该元素中,只有job是自己定义的,其他全部按照如上所示的规范填写。
* taskId:taskId表示在任务维护中,定义的任务的ID,创建的job执行的类实际上是根据任务中定义的类,所以请确保此处填写无误。
* scheduleName:scheduleName表示任务周期中定义的周期名,任务会按照scheduleName对应的周期执行。
* defineStartTime:自定义的任务开始时间,如果有需要则填写,否则直接根据周期定义中的开始时间执行。
* defineEndTime:自定义的任务结束时间,如果有需要则填写,否则直接根据周期定义中的时间结束。
* businessParam:业务所需的参数,填写后可以在任务执行时获取到。
\ No newline at end of file
# 融租易开发手册 # 融租易开发手册
* [GitBook使用](/gitbook.md) * [GitBook使用](/gitbook.md)
--- ---
* I. 开发环境准备 * I. 开发环境准备
* [1.1 Git 使用](/git-guide.md "git指令") * [1.1 Git 使用](/git-guide.md "git指令")
* [1.2 Maven 使用](/maven.md "maven使用") * [1.2 Maven 使用](/maven.md "maven使用")
* [1.3 开发环境搭建](/project-create.md) * [1.3 开发环境搭建](/project-create.md)
* [1.4 更新项目的HEL依赖版本](/project-update.md) * [1.4 更新项目的HEL依赖版本](/project-update.md)
* [1.5 Liquibase](/liquibase-use.md) * [1.5 Liquibase](/liquibase-use.md)
* II. 项目开发规范 * II. 项目开发规范
* [2.1 项目开发规范](/codeStyle.md#backEndDev) * [2.1 项目开发规范](/codeStyle.md#backEndDev)
* [2.2 编码规范](/codeStyle.md#backEndName) * [2.2 编码规范](/codeStyle.md#backEndName)
* 2.3 Checkstyle * 2.3 Checkstyle
* III. 后端开发 * III. 后端开发
* IV. 前端JavaScript开发 * IV. 前端JavaScript开发
* [4.1 前端开发说明 \(1.0\)](/front-kendoui.md) * [4.1 前端开发说明 \(1.0\)](/front-kendoui.md)
* [4.2 字段级通用方法](/common-field-javascript.md) * [4.2 字段级通用方法](/common-field-javascript.md)
* [4.3 窗口级通用方法](/common-window-javascript.md) * [4.3 窗口级通用方法](/common-window-javascript.md)
* [4.4 锁屏和解屏通用方法](/common-mask-javascript.md)
* V. 前端UI开发
* V. 前端UI开发
* [5.1 hlsCombobox(下拉框)](/前端组件/hlsCombobox.md)
* [5.2 hlsDataSource](/前端组件/hlsDataSource.md) * [5.1 hlsCombobox(下拉框)](/前端组件/hlsCombobox.md)
* [5.3 hlsMaskedTextBox](/前端组件/hlsMaskedTextBox.md) * [5.2 hlsDataSource](/前端组件/hlsDataSource.md)
* [5.4 hlsPage](/前端组件/hlsPage.md) * [5.3 hlsMaskedTextBox](/前端组件/hlsMaskedTextBox.md)
* [5.5 hlsTextArea](/前端组件/hlsTextArea.md) * [5.4 hlsPage](/前端组件/hlsPage.md)
* [5.6 hlsTlEdit](/前端组件/hlsTlEdit.md) * [5.5 hlsTextArea](/前端组件/hlsTextArea.md)
* [5.7 hlsDatePicker](/前端组件/hlsDatePicker.md) * [5.6 hlsTlEdit](/前端组件/hlsTlEdit.md)
* [5.8 hlsDateTimePicker](/前端组件/HlsDateTimePicker.md) * [5.7 hlsDatePicker](/前端组件/hlsDatePicker.md)
* [5.9 hlsLov](/前端组件/HlsLov.md) * [5.8 hlsDateTimePicker](/前端组件/HlsDateTimePicker.md)
* [5.10 hlsToolBar](/前端组件/HlsToolBar.md) * [5.9 hlsLov](/前端组件/HlsLov.md)
* [5.11 TabStrip](/前端组件/TabStrip.md) * [5.10 hlsToolBar](/前端组件/HlsToolBar.md)
* [5.12 hlsForm](/前端组件/hlsForm.md) * [5.11 TabStrip](/前端组件/TabStrip.md)
* [5.13 hlsCombobox](/前端组件/HlsCombobox.md) * [5.12 hlsForm](/前端组件/hlsForm.md)
* [5.14 hlsCheckBox](/前端组件/HlsCheckBox.md) * [5.13 hlsCombobox](/前端组件/HlsCombobox.md)
* [5.15 抽屉通用方法](/前端组件/box.md) * [5.14 hlsCheckBox](/前端组件/HlsCheckBox.md)
* [5.16 Grid](/前端组件/Grid.md) * [5.15 Grid](/前端组件/Grid.md)
* [5.17 DataSource](/前端组件/DataSource.md) * [5.16 DataSource](/前端组件/DataSource.md)
* [5.18 hlsGridBox](/前端组件/hlsGridBox.md) * [5.17 hlsGridBox](/前端组件/hlsGridBox.md)
* [5.19 NumericTextBx](/前端组件/NumericTextBox.md) * [5.18 NumericTextBx](/前端组件/NumericTextBox.md)
* [5.20 HlsNavigationBar](/前端组件/HlsNavigationBar.md) * [5.19 HlsNavigationBar](/前端组件/HlsNavigationBar.md)
* VI. 框架功能描述 * VI. 框架功能描述
* [6.1 计划任务](/框架功能描述/计划任务.md) * [6.1 计划任务](/框架功能描述/计划任务.md)
* [6.2 RabbitMq消息队列](/后端开发/RabbitMq消息队列.md) * [6.2 RabbitMq消息队列](/后端开发/RabbitMq消息队列.md)
* [6.3 应用服务部署(tomcat/weblogic)](/框架功能描述/deployment.md) * [6.3 应用服务部署(tomcat/weblogic)](/框架功能描述/deployment.md)
* [6.4 合同文本生成](/docx4j.md) * [6.4 合同文本生成](/docx4j.md)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment