首先搭建流程设计器
官网下载activiti6.0,下载下来之后解压后找到activiti-app.war文件放入本地tomcat中运行,访问路径为(localhost:8080/activiti-app),账号:admin 密码:test
修改设计器数据库,设计器默认的使用的是h2数据库,需要修改tomcat中项目\activiti-app\WEB-INF\classes\META-INF\activiti-app\activiti-app.properties,选择需要的数据库设置
#datasource.driver=org.h2.Driver
#datasource.url=jdbc:h2:mem:activiti;DB_CLOSE_DELAY=-1
datasource.driver=com.mysql.jdbc.Driver
datasource.url=jdbc:mysql://**********:3306/****?characterEncoding=UTF-8
datasource.username=****
datasource.password=****
搭建activiti项目(该项目使用springboot,所以和常规搭建可能稍有不同)
- 引入pom
org.activiti
activiti-spring-boot-starter-basic
6.0.0.RC1
- 关闭activiti自动部署(因为我们使用流程设计器部署,不使用具体文件访问方式),在application.properties中加入以下代码
spring.activiti.check-process-definitions=false
-
设计流程
遇到问题:
在设计流程中我们发现在定义审批人时遇到了一个无法忽略的问题————无法找到指定审批人,意思是设置了一个审批人的参数,没办法根据申请人获取到相应的审批人,或许你会觉得根据申请人的部门查找不就行了吗,但是像财务、董事长之类的在部门之上的的部门和人,就没办法找对并且获取了。
解决:
暂时的办法是将每个部门的所有流程全部分开,每个审批人使用角色定义,这就解决了获取不到对应审批人的问题,但是紧接着又有一个问题,申请人如何才能申请到自己部门的对应流程呢,又是一个大问题,问题好像又回到了原点,但是有何开始的问题有一些不同,从同一个流程获得不同的用户角色,变成了从一个部门获取相应部门流程,那就简单了,只需要创建一个表将他们对应起来就可以了,但是这涉及到三个字段,一个是部门id,一个是流程id,还有一个类型id(就是这个流程是干什么的,借款、报销。。。),三个信息组成一条对应流程片,好像有点复杂,管理页面也有点难做。
举个例子:
从这张图上能清楚的看出我们面临的问题,因为每个部门都有同样的流程类型,但是又不同的流程具体实现,如何才能从三个选择减少成两个甚至一个呢?
鉴于这一点,我们只能在流程id上做手脚了,将流程id根据不同的类型使用不同的名字前缀,这样就从三个字段变成了两个,例如:流通部门国内借款流程,我们将id设为 gnjk_ltjk ,这样我们就能根据id的前缀gnjk查到该流程为国内借款类型。可能有人会问为什么不直接将部门也放在流程id里面,这样就不用创建表了,也想过这个问题,而且也可以这样做,但未免太死板了,完全不给以后融合流程机会了,毕竟现在一个部门就要n条流程还是有弊端的,就是重复流程过多的问题,所以先暂时这样吧
-
表设计
在正式走流程之前还有一个要做,就是业务表的设计,尽管activiti自带了大量的表,但是还是要设计关于自己项目的业务表才行。具体的业务表就不详细说了,无非是报销、借款。。。之类的,但是有一个小技巧(前人经验),因为具体业务的不同,所有会有多个表,在审批时查找起来会相当的麻烦,而且也不易于列表展示,所以提出一个共有的审核主表,该主表有流程id、业务id和具体业务的简要信息,这样才查找审批列表时直接来一个表中查找就可以了,而在审批需要查看详情时,又可以根据不同的类型去对应的表中查找审批,相当方便。
CREATE TABLE `s_auditor_core` (
`id` varchar(50) NOT NULL COMMENT '使用nextval函数获取',
`business_id` varchar(50) DEFAULT NULL COMMENT '业务id',
`process_id` varchar(64) DEFAULT NULL COMMENT '流程id',
`create_user` bigint(10) DEFAULT NULL COMMENT '用户id',
`create_date` datetime DEFAULT NULL COMMENT '创建时间',
`sketch` varchar(255) DEFAULT NULL COMMENT '备注',
`apply_type` varchar(255) DEFAULT NULL COMMENT '申请类型(和权限表中的权限一致)',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
-
启动流程
因为用的是springboot,所以不需要手动的创建xml文件,然后配置一大堆东西,只需要引包,然后再需要使用的地方声明接口就好了,非常的方便。
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
@Autowired
private RepositoryService repositoryService;
然后在获得对应的流程id后,将业务详细信息,流程,审批主表一次执行保存,一次完整的流程启动就完成了。
*
* @param bGnjk 国内借款业务参数
* @param htmlSign 模块标记,这个为借款、报销。。。类型
* @return
* @throws Exception
*/
public CommonDomain gnjkProcessStart(BGnjk bGnjk,String htmlSign)throws Exception {
CommonDomain commonDomain = new CommonDomain<>();
//配置activiti需要参数
Map m = new HashMap();
m.put(ACTKeyTool.MONEY,bGnjk.getMoney());
List process = sUserMapper.getProcessByUser(bGnjk.getCreateUser().intValue(),htmlSign);
if(process==null||process.size()!=1){
throw new Exception("流程启动获得多条流程");
}
//启动activiti
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(process.get(0).getProcess_key(),m);
//保存业务数据
bGnjkMapper.insertSelective(bGnjk);
//保存审批主表
SAuditorCore sAuditorCore = new SAuditorCore();
sAuditorCore.setApplyType(htmlSign);
sAuditorCore.setBusinessId(bGnjk.getId());
sAuditorCore.setCreateDate(new Date());
sAuditorCore.setCreateUser(bGnjk.getCreateUser());
sAuditorCore.setProcessId(processInstance.getId());
sAuditorCore.setSketch(bGnjk.getDescribe());
SAuditorCoreMapper.insertSelective(sAuditorCore);
//返回结果封装
commonDomain.setStatuscode(PublicMsg.ResultCode.SUCCESS.value());
commonDomain.setMsg(PublicMsg.SUCCESS_INFO);
return commonDomain;
}
当完成这一步的时候,业务详细表中有了业务的数据,act_ru_task中有个流程信息,并指向第一个usertask节点--流通部门经理,审批主表中有业务详细表和流程信息表的唯一id,并将它们联系起来,现在开始,流程就正式启动了
-
审批流程
不推荐使用activiti的taskService原生api,因为一般每个项目都会有自己的用户表,虽然activiti支持替换用户表,但是配置起来貌似有些麻烦,所以根本没有鸟他。下面是一些taskService常用的查询方法源码
query.taskCandidateGroup 源码:
SELECT DISTINCT
RES.*
FROM
ACT_RU_TASK RES
INNER JOIN ACT_RU_IDENTITYLINK I
ON I.TASK_ID_ = RES.ID_
WHERE RES.ASSIGNEE_ IS NULL
AND I.TYPE_ = 'candidate'
AND (I.GROUP_ID_ IN (?))
query.taskCandidateOrAssigned 源码:
select distinct RES.* from ACT_RU_TASK RES
left join ACT_RU_IDENTITYLINK I on I.TASK_ID_ = RES.ID_
WHERE (
RES.ASSIGNEE_ = ?
or (
RES.ASSIGNEE_ is null
and (
I.USER_ID_ = ?
or
I.GROUP_ID_ IN (
select g.GROUP_ID_ from ACT_ID_MEMBERSHIP g where g.USER_ID_ = ?
)
)
)
)
在源码中可以看到基本上查的是ACT_RU_TASK和ACT_RU_IDENTITYLINK表,因为我们根本没有用activit的用户表,所以如果使用它的api会让sql查询变得奇怪起来,因为我们在设计流程的时候已经指定了会使用角色来充当审批人,那么这个角色只会出现在ACT_RU_IDENTITYLINK表的USER_ID_字段上,下面是我根据我们的实际业务编写的sql
SELECT
s.`name`apply_type, -- 类型名称
a.`business_id`, -- 业务详细表id
a.`create_date`, -- 申请时间
u.`name` user, -- 申请人
a.`id`, -- 审批主表d
a.`process_id`, -- 流程id
a.`sketch`, -- 简介
i.`TASK_ID_` task_id -- 任务id
FROM
`act_ru_task` t
JOIN `act_ru_identitylink` i
ON t.`ID_` = i.`TASK_ID_`
JOIN `s_auditor_core` a ON a.process_id=t.PROC_INST_ID_
JOIN `s_permission` s ON a.`apply_type`=s.`permission`
JOIN `s_user` u ON u.`id`=a.`create_user`
WHERE FIND_IN_SET(i.`USER_ID_`, #{roles})
order by a.`create_date` desc
其中s_auditor_core为审批主表,s_permission申请类型(客串,其实是权限表),s_user用户表,act_ru_task任务表,act_ru_identitylink任务关联人员表,而传进来的#{roles}也是一个登录人的所有角色集合
接下来就是审批的具体实施了,因为觉得不需要再有一个审批意见表,所以使用act_hi_comment来代替:
设置/获得 审批人和意见
设置:
审批人一般为登录人
Authentication.setAuthenticatedUserId("审批人");
taskService.addComment(task.getId(), task.getProcessInstanceId(), "批注");
获得:
List hais = historyService
.createHistoricActivityInstanceQuery()
.processInstanceId(id)
.activityType("userTask").list();
for (HistoricActivityInstance hai : hais) {
String historytaskId = hai.getTaskId();
List comments = taskService.getTaskComments(historytaskId);
for (Comment comment : comments) {
System.out.println(comment.getFullMessage()+"ren"+comment.getUserId());
}
在同意时使用正常的审批就ok了:
taskService.complete(task.getId());
但是在拒绝时需要特殊操作:
删除正在运行的流程,主要用于拒绝操作
runtimeService.deleteProcessInstance(流程实力id,删除原因(可为空))
为什么要这样做:
因为activit并不支持突然的拒绝操作,如果想要实现这样的效果,就要在每条审批中设置一条拒绝的流程线并指向结束节点,而且还必须要用到排他网管,不然流程就不会结束,所以,鉴于这个情况的出现,我们采用直接删除流程,但流程的历史信息不回被删除,而且流程也会被置为结束,只需要在审批前填写流程审批意见就行了
遗留问题
- 将各部门流程融合成一个,现在重复太多不便管理
- 获取下一个审批人,因为换成activiti6.0后,pvm包被整体移除,所以原来的方式好像行不通了
- BpmnModel 操作