Activiti 转载文章
整理从其他网站转载比较好的文章
- Activiti7的核心详解
- Liferay-Activiti 功能介绍 (新版Liferay7基本特性)
- Liferay-Activiti 企业特性功能介绍(新版Liferay7)
- 比较Activiti中三种不同的表单及其应用
- [Activiti 6.x] Springboot 1.5x MySQL
- [Activiti 6.x] 基本流程讲解与开发前奏
- [Activiti 6.x] 核心API基础
- [Activiti 6.x] 基础流程DEMO
- [Activiti 6.x] 剩下的核心API
Activiti7的核心详解
Activiti7的核心详解
流程定义
流程定义是线下按照bpmn2.0标准去描述业务流程,通常使用activiti-explorer(web控制台)或activiti-eclipse-designer插件对业务流程进行建模,这两种方式都遵循bpmn2.0标准。
使用activiti-desinger设计业务流程,会生成.bpmn文件
.png图片需手动生成,在IDEA中步骤如下:
- 1)将holiday.bpmn文件改为扩展名xml的文件名称:holiday.xml
- 2)在holiday.xml文件上面,点右键并选择Diagrams菜单,再选择Show BPMN2.0 Designer...
- 3)点击Export To File的小图标,选择好保存图片的位置
- 4)中文乱码的解决
打开IDEA安装路径,找到如下的安装目录,在idea64.exe.vmoptions文件的最后一行追加一条命令:-Dfile.encoding=UTF-8
-Xms128m
-Xmx750m
-XX:ReservedCodeCacheSize=240m
-XX:+UseConcMarkSweepGC
-XX:SoftRefLRUPolicyMSPerMB=50
-ea
-Dsun.io.useCanonCaches=false
-Djava.net.preferIPv4Stack=true
-XX:+HeapDumpOnOutOfMemoryError
-XX:-OmitStackTraceInFastThrow
-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2018.1.4\bin\JetbrainsCrack-2.8-release-enc.jar
-Dfile.encoding=UTF-8
一定注意,不要有空格,否则重启IDEA时会打不开,然后重启IDEA,把原来的png图片删掉,再重新生成,即可解决乱码问题
1.什么是流程定义部署
将线下定义的流程部署到activiti数据库中,这就是流程定义部署,通过调用activiti的api将流程定义的bpmn和png两个文件一个一个添加部署到activiti中,也可以将两个文件打成zip包进行部署。
单个文件部署方式:
/**
* 流程定义的部署
* activiti表有哪些?
* act_re_deployment 流程定义部署表,记录流程部署信息
* act_re_procdef 流程定义表,记录流程定义信息
* act_ge_bytearray 资源表(bpmn文件及png文件)
*/
@Test
public void createDeploy() {
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("diagram/holiday.bpmn")//添加bpmn资源
.addClasspathResource("diagram/holiday.png")
.name("请假申请单流程")
.deploy();
log.info("流程部署id:" + deployment.getName());
log.info("流程部署名称:" + deployment.getId());
}
2.流程定义查询
@Test
public void queryProcessDefinition() {
String processDefinitionKey = "holiday";
RepositoryService repositoryService = processEngine.getRepositoryService();
//查询流程定义
ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
List<ProcessDefinition> list = processDefinitionQuery.processDefinitionKey(processDefinitionKey)
.orderByProcessDefinitionVersion().desc().list();
for (ProcessDefinition processDefinition : list) {
log.info("------------------------");
log.info("流 程 部 署id:" + processDefinition.getDeploymentId());
log.info("流程定义id:" + processDefinition.getId());
log.info("流程定义名称:" + processDefinition.getName());
log.info("流程定义key:" + processDefinition.getKey());
log.info("流程定义版本:" + processDefinition.getVersion());
}
}
3.流程定义删除
/**
* 删除已经部署成功的流程定义
* 背后影响的表:
* act_ge_bytearray
* act_re_deployment
* act_re_procdef
*/
@Test
public void deleteDeployment() {
String deploymentId = "a2833cf7-10bb-11ea-9ac9-00155d65d6c0";
RepositoryService repositoryService = processEngine.getRepositoryService();
//删除流程定义,如果该流程定义已有流程实例启动则删除时出错
// repositoryService.deleteDeployment(deploymentId);
//设置true 级联删除流程定义,即使该流程有流程实例启动也可以删除,设置为false非级别删除方式,如果流程
repositoryService.deleteDeployment(deploymentId, true);
}
流程实例
1.什么是流程实例
参与者(可以是用户也可以是程序)按照流程定义内容发起一个流程,这就是一个流程实例。
2.启动流程实例
流程定义部署在activiti后,就可以在系统中通过activiti去管理该流程的执行,执行流程表示流程的一次执行。比如部署系统请假流程后,如果某用户要申请请假这时就需要执行这个流程,如果另外一个用户也要申请请假则也需要执行该流程,每个执行互不影响,每个执行是单独的流程实例。
@Test
public void startProcessInstance() {
RuntimeService runtimeService = processEngine.getRuntimeService();
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holiday");
log.info("流程定义ID:" + processInstance.getProcessDefinitionId());
log.info("流程实例ID:" + processInstance.getId());
}
3.Businesskey(业务标识)
启动流程实例时,指定的businesskey,就会在act_ru_execution #流程实例的执行表中存储businesskey。
Businesskey:业务标识,通常为业务表的主键,业务标识和流程实例一一对应。业务标识来源于业务系统。存储业务标识就是根据业务标识来关联查询业务系统的数据。 比如:请假流程启动一个流程实例,就可以将请假单的id作为业务标识存储到activiti中,将来查询activiti的流程实例信息就可以获取请假单的id从而关联查询业务系统数据库得到请假单信息。
@Test
public void startProcessInstance() {
RuntimeService runtimeService = processEngine.getRuntimeService();
//启动流程实例,同时还要指定业务标识businessKey 它本身就是请假单的id
//第一个参数:是指流程定义key
//第二个参数:业务标识businessKey
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holiday", "1001");
String businessKey = processInstance.getBusinessKey();
System.out.println("businessKey:" + businessKey);
}
4.操作数据库表
ct_ru_execution #流程实例执行表,记录当前流程实例的执行情况,一个流程实例运行完成,此表中与流程实例相关的记录删除。
ct_ru_task #任务执行表,记录当前执行的任务,启动流程实例,流程当前执行到第一个任务结点,此表会插入一条记录表示当前任务的执行情况,如果任务完成则记录删除。
ct_ru_identitylink #任务参与者,记录当前参与任务的用户或组
ct_hi_procinst #流程实例历史表,流程实例启动,会在此表插入一条记录,流程实例运行完成记录也不会删除。
ct_hi_taskinst #任务历史表,记录所有任务,开始一个任务,不仅在act_ru_task表插入记录,也会在历史任务表插入一条记录,任务历史表的主键就是任务id,任务完成此表记录不删除。
ct_hi_actinst #活动历史表,记录所有活动
5.查询流程实例
@Test
public void queryProcessInstance() {
String processDefinitionKey = "holiday";
RuntimeService runtimeService = processEngine.getRuntimeService();
List<ProcessInstance> list = runtimeService.createProcessInstanceQuery()
.processDefinitionKey(processDefinitionKey).list();
for (ProcessInstance processInstance : list) {
System.out.println("-----------------------------");
System.out.println("流程实例id:" + processInstance.getProcessInstanceId());
System.out.println("所属流程定义id:" + processInstance.getProcessDefinitionId());
System.out.println("是否执行完成:" + processInstance.isEnded());
System.out.println("是否暂停:" + processInstance.isSuspended());
}
}
个人任务
1.分配任务负责人
- 1)固定分配
在进行业务流程建模时指定固定的任务负责人,在properties视图中,填写Assignee项为任务负责人。
由于固定分配方式,任务只管一步一步执行任务,执行到每一个任务将按照bpmn的配置去分配任务负责人。
- 2)表达式分配
Activiti使用UEL(统一表达式语言)表达式,activiti支持两个UEL表达式:UEL-value和UEL-method
assignee这个变量是activiti的一个流程变量
UEL-method方式如下:
ldapService是spring容器的一个bean,findManagerForEmployee是该bean的一个方法,emp是activiti流程变量,emp作为参数传到ldapService.findManagerForEmployee方法中。
其它:表达式支持解析基础类型、bean、list、array和map,也可作为条件判断。如下:${order.price > 100 && order.price < 250}
使用流程变量分配任务
@Test
public void assignVariables() {
RuntimeService runtimeService = processEngine.getRuntimeService();
//设置assignee的取值,用户可以在界面上设置流程的执行人
Map<String, Object> map = new HashMap<String, Object>();
map.put("assignee0", "张三");
map.put("assignee1", "李四");
map.put("assignee2", "王五");
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holiday2", map);
System.out.println(processInstance.getProcessInstanceId());
}
- 3)监听器分配
任务监听器是发生对应的任务相关事件时执行自定义java逻辑或表达式
- Create:任务创建后触发
- Assignment:任务分配后触发
- Delete:任务完成后触发
- All:所有事件发生都触发
定义任务监听类,且类必须实现org.activiti.engine.delegate.TaskListener接口
public class MyTaskListener implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
delegateTask.setAssignee("赵六");
}
}
- 4)查询任务
@Test
public void findPersonalTaskList() {
String processDefinitionKey = "holiday2";
String assignee = "赵六";
TaskService taskService = processEngine.getTaskService();
List<Task> taskList = taskService.createTaskQuery()
.processDefinitionKey(processDefinitionKey)
.includeProcessVariables().taskAssignee(assignee).list();
for (Task task : taskList) {
System.out.println("--------------------------------------");
System.out.println("流程实例id:" + task.getProcessInstanceId());
System.out.println("任务id:" + task.getId());
System.out.println("任务负责人:" + task.getAssignee());
System.out.println("任务名称:" + task.getName());
}
}
流程变量
1.什么是流程变量
流程变量就是activiti在管理工作流时根据管理需要而设置的变量。
比如在请假流程流转时如果请假天数大于3天则由总经理审核,否则由人事直接审核,请假天数就可以设置为流程变量,在流程流转时使用。
2.流程变量作用域
流程变量的作用域默认是一个流程实例(processInstance),也可以是一个任务(task)或一个执行实例(execution),这三个作用域流程实例的范围最大,可以称为global变量,任务和执行实例仅仅是针对一个任务和一个执行实例范围,范围没有流程实例大,称为local变量。
3.流程变量的使用方法
-
1)设置流程变量
-
2)通过UEL表达式使用流程变量
4.使用Global变量控制流程
员工创建请假申请单,由部门经理审核,部门经理审核通过后请假3天及以下由人事经理直接审核,3天以上先由总经理审核,总经理审核通过再由人事经理存档。
请假天数大于3连线条件
- 1)启动流程时设置
在启动流程时设置流程变量,变量的作用域是整个流程实例。通过map<key,value>设置流程变量,map中可以设置多个变量,这个key就是流程变量的名字。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Holiday implements Serializable {
//必须实现序列化接口serializable,为了防止由于新增字段无法反序列化,需要生成serialVersionUID
private static final long serialVersionUID = 5707634407289856169L;
private Integer id;
private String holidayName;//申请人的名字
private Date beginDate;//开始时间
private Date endDate;//结束日期
private Float num;//请假天数
private String reason;//事由
private String type;//请假类型
}
/**
* 启动流程时设置流程变量
* act_ge_bytearray
* act_ru_variable
*/
@Test
public void startProcessInstance() {
String processDefinitionKey = "holiday4";
Holiday holiday = new Holiday();
holiday.setNum(5F);
//定义流程变量
Map<String, Object> variables = new HashMap<>();
variables.put("holiday", holiday);
RuntimeService runtimeService = processEngine.getRuntimeService();
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefinitionKey, variables);
System.out.println("流程实例id:" + processInstance.getProcessInstanceId());
}
- 2)任务办理时设置
在完成任务时设置流程变量,该流程变量只有在该任务完成后其它结点才可使用该变量,它的作用域是整个流程实例,如果设置的流程变量的key在流程实例中已存在相同的名字则后设置的变量替换前边设置的变量。
@Test
public void completeTask() {
TaskService taskService = processEngine.getTaskService();
String key = "holiday4";
String assignee = "张三";
Task task = taskService.createTaskQuery().processDefinitionKey(key).
taskAssignee(assignee).singleResult();
Holiday holiday = new Holiday();
holiday.setNum(4F);
//初始化一些参数
Map<String, Object> map = new HashMap<>();
map.put("holiday", holiday);
if (task != null) {
taskService.complete(task.getId(), map);//完成任务时,设置流程变量的值
System.out.println("任务执行完毕");
}
}
- 3)通过当前流程实例设置
@Test
public void setGlobalVariableByExecutionId() {
String executionId = "f789207c-0aa2-11ea-9a53-00155d65d6c0";
RuntimeService runtimeService = processEngine.getRuntimeService();
Holiday holiday = new Holiday();
holiday.setNum(3F);
runtimeService.setVariable(executionId, "holiday", holiday);
System.out.println(runtimeService.getVariable(executionId, "holiday"));
}
executionId必须当前未结束流程实例的执行id,通常此id设置流程实例的id。也可以通过runtimeService.getVariable()获取流程变量
- 4)通过当前任务设置
@Test
public void setGlobalVariableByTaskId() {
String taskId = "4d47161e-0aa4-11ea-aea8-00155d65d6c0";
TaskService taskService = processEngine.getTaskService();
Holiday holiday = new Holiday();
holiday.setNum(5F);
Map<String, Object> variables = new HashMap<>();
variables.put("holiday", holiday);
taskService.setVariable(taskId, "holiday", holiday);
System.out.println(taskService.getVariable(taskId, "holiday"));
}
5.设置local流程变量
- 1)任务办理时设置
任务办理时设置local流程变量,当前运行的流程实例只能在该任务结束前使用,任务结束该变量无法在当前流程实例使用,可以通过查询历史任务查询。
@Test
public void completeTaskLocal() {
String taskId = "676c8a5d-0b35-11ea-a44a-00155d65d6c0";
Map<String, Object> variables = new HashMap<>();
Holiday holiday = new Holiday();
holiday.setNum(3F);
variables.put("holiday", holiday);
TaskService taskService = processEngine.getTaskService();
//设置local变量,作用域为该任务
taskService.setVariablesLocal(taskId, variables);
taskService.complete(taskId);
}
设置作用域为任务的local变量,每个任务可以设置同名的变量,互不影响。
6.查询历史流程变量
@Test
public void queryHistoricLocalVariables() {
HistoryService historyService = processEngine.getHistoryService();
HistoricTaskInstanceQuery historicTaskInstanceQuery = historyService.createHistoricTaskInstanceQuery();
List<HistoricTaskInstance> list = historicTaskInstanceQuery.includeTaskLocalVariables()
.finished().list();
for (HistoricTaskInstance hti : list) {
System.out.println("============================");
System.out.println("任务id:" + hti.getId());
System.out.println("任务名称:" + hti.getName());
System.out.println("任务负责人:" + hti.getAssignee());
System.out.println("任务local变量:" + hti.getTaskLocalVariables());
}
}
组任务
1.Candidate-users候选人
在流程定义中在任务结点的assignee固定设置任务负责人,在流程定义时将参与者固定设置在.bpmn文件中,如果临时任务负责人变更则需要修改流程定义,系统可扩展性差。针对这种情况可以给任务设置多个候选人,可以从候选人中选择参与者来完成任务。
在流程图中任务节点的配置中设置candidate-users(候选人),多个候选人之间用逗号分开。
2.办理组任务
- 1)用户查询组任务
@Test
public void findGroupTaskList() {
String processDefinitionKey = "holiday4";
String candidateUser = "李四";
TaskService taskService = processEngine.getTaskService();
List<Task> taskList = taskService.createTaskQuery().processDefinitionKey(processDefinitionKey)
.taskCandidateUser(candidateUser).list();//根据候选人查询
for (Task task : taskList) {
System.out.println("---------------------");
System.out.println("流程实例id:" + task.getProcessInstanceId());
System.out.println("任务id:" + task.getId());
System.out.println("任务负责人:" + task.getAssignee());
System.out.println("任务名称:" + task.getName());
}
}
- 2)用户拾取组任务
/**
* 用户拾取组任务,该任务变为自己的个人任务
*/
@Test
public void claimTask() {
TaskService taskService = processEngine.getTaskService();
String taskId = "a7ca3538-0b69-11ea-a1d2-00155d65d6c0";
String userId = "李四";
//校验该用户有没有拾取任务的资格
Task task = taskService.createTaskQuery().taskId(taskId)
.taskCandidateUser(userId).singleResult();//根据候选人查询
if (task != null) {
taskService.claim(taskId, userId);
System.out.println("任务拾取成功");
}
}
- 3)用户办理个人任务
@Test
public void completeTask() {
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery().processDefinitionKey("holiday4")
.taskAssignee("张三").singleResult();
if (task != null) {
taskService.complete(task.getId());
System.out.println("用户任务执行完毕...");
}
}
- 4)归还组任务
如果个人不想办理该组任务,可以归还组任务,归还后该用户不再是该任务的负责人
/**
* 归还组任务,由个人任务变为组任务,还可以进行任务交接
*/
@Test
public void setAssigneeToGroupTask() {
TaskService taskService = processEngine.getTaskService();
String taskId = "a7ca3538-0b69-11ea-a1d2-00155d65d6c0";
String userId = "李四";
//校验userId是否是taskId的负责人,如果是负责人才可以归还组任务
Task task = taskService.createTaskQuery().taskId(taskId)
.taskAssignee(userId).singleResult();
if (task != null) {
//如果设置为null,归还组任务,该任务没有负责人
taskService.setAssignee(taskId, null);
}
}
- 5)任务交接
/**
* 任务交接,前提要保证当前用户是这个任务的负责人,这时候他才可以有权限去将任务交接给其他候选人
*/
@Test
public void setAssigneeToCandidateUser() {
TaskService taskService = processEngine.getTaskService();
String taskId = "a7ca3538-0b69-11ea-a1d2-00155d65d6c0";
String userId = "李四";
//校验userId是否是taskId的负责人,如果是负责人才可以归还组任务
Task task = taskService.createTaskQuery().taskId(taskId)
.taskAssignee(userId).singleResult();
if (task != null) {
//将此任务交给其它候选人办理该任务
String candidateUser = "张三";
//根据候选人和组任务id查询,如果有记录说明该候选人有资格拾取该任务
Task task1 = taskService.createTaskQuery().taskCandidateUser(candidateUser).singleResult();
if (task1 != null) {
taskService.setAssignee(taskId, candidateUser);
}
}
}
网关
1.排他网关
- 1)什么是排他网关
排他网关(也叫异或(XOR)网关,或叫基于数据的排他网关),用来在流程中实现决策。当流程执行到这个网关,所有分支都会判断条件是否为true,如果为true则执行该分支,注意,排他网关只会选择一个为true的分支执行。(即使有两个分支条件都为true,排他网关也会只选择一条分支去执行)
- 2)流程定义
在部门经理审核后,走排他网关,从排他网关出来的分支有两条,一条是判断请假天数是否大于3天,另一条是判断请假天数是否小于等于3天。
- 3)测试
流程定义部署-->启动流程设置流程变量-->执行任务
@Test
public void completeTask() {
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery().processDefinitionKey("holiday5_1")
.taskAssignee("lisi").singleResult();
if (task != null) {
taskService.complete(task.getId());
System.out.println("用户任务执行完毕...");
}
}
2.并行网关
- 1)什么是并行网关
并行网关允许将流程分成多条分支,也可以把多条分支汇聚到一起,并行网关的功能是基于进入和外出顺序流的:
-
fork分支
并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。
-
join汇聚
所有到达并行网关,在此等待的进入分支,直到所有进入顺序流的分支都到达以后,流程就会通过汇聚网关。
如果同一个并行网关有多个进入和多个外出顺序流,它就同时具有分支和汇聚功能。这时,网关会先汇聚所有进入的顺序流,然后再切分成多个并行分支。与其他网关的主要区别是,并行网关不会解析条件。即使顺序流中定义了条件,也会被忽略
财务结算和行政考勤是两个execution分支,在act_ru_execution表有两条记录分别是财务结算和行政考勤,act_ru_execution还有一条记录表示该流程实例。待财务结算和行政考勤任务全部完成,在汇聚点汇聚,通过parallelGateway并行网关。并行网关在业务应用中常用于会签任务,会签任务即多个参与者共同办理的任务。
- 2)流程定义
- 3)测试
流程定义部署-->启动流程设置流程变量-->执行任务
@Test
public void completeTask() {
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery().processDefinitionKey("holidayParallel")
.taskAssignee("zhangsan").singleResult();
if (task != null) {
taskService.complete(task.getId());
System.out.println("用户任务执行完毕...");
}
}
3.包含网关
- 1)什么是包含网关
包含网关可以看做是排他网关和并行网关的结合体。和排他网关一样,你可以在外出顺序流上定义条件,包含网关会解析它们。但是主要的区别是包含网关可以选择多于一条顺序流,这和并行网关一样。 包含网关的功能是基于进入和外出顺序流的:
-
分支
所有外出顺序流的条件都会被解析,结果为true的顺序流会以并行方式继续执行,会为每个顺序流创建一个分支
-
汇聚
所有并行分支到达包含网关,会进入等待状态,直到每个包含流程token的进入顺序流的分支都到达。这是与并行网关的最大不同。换句话说,包含网关只会等待被选中执行了的进入顺序流。在汇聚之后,流程会穿过包含网关继续执行。
-
2)流程定义
企业体检流程,公司全体员工进行常规项检查、抽血化验,公司管理层除常规检查和抽血化验还要进行增加项检查。
员工类型:通过流程变量userType来表示,如果等于1表示普通员工,如果等于2表示领导
- 3)测试
Liferay-Activiti 功能介绍 (新版Liferay7基本特性)
一句话简介
Liferay是世界领先的开源企业门户(也可作为综合门户),是**最强大(没有之一)**的JAVA开源门户,在Gartner和Forrester和评价非常高,近几年已经超越了微软门户Sharepoint。
Activiti是著名开源jBPM的分支,创始人Tom Baeyens就是JBoss jBPM的项目架构师,以及jBPM另一位架构师Joram Barrez,一起创建的Alfresco公司开发的BPM工作流平台。
商业模式
Liferay和Activiti都分社区开源版 、企业版2种,内核代码都是一样的,区别在于收费插件和企业服务支持,从社区版到商业版可无缝转换。
为什么要选择Liferay和Activiti
作为IT技术负责人的你是否有以下选型烦恼:
- 选国内闭源产品(比如蓝凌OA),受制于大厂,费用高,周期慢,因产品框架限制不一定能实现较特殊的需求;
- 选的技术太旧(比如东软开发平台),开发出的项目,开发扩展困难,和其他业务系统集成困难,开发人员抵触;
- 选国内假开源产品,技术能力参差不齐,产品包装的很牛,但用起来各种细节问题;
为什么选基于Liferay + Activiti 开发企业门户产品
- Liferay是最优秀的开源企业门户,使用企业数千,使用者数千万,产品稳定性和先进度有保障,功能极具扩展性;
- Liferay移动端界面支持好(H5自适应);
- Liferay自身的BPM较弱,所以需要集成较强的BPM,而Activiti是非常优秀的BPM产品,
- Activiti 使用者众多,功能灵活,上手难度不大,它的前身jBPM在jBoss控制下越来越重,所以不选jBPM;
- Activiti BPM满足国际BPMN2.0规范,选用Activiti BPM在流程迁移方面有规范、风险低;
- 有代码,不会受制于人,更可控;
- 开发风险可控,最核心的2个功能(门户平台和BPM)依托成熟开源,稳定性不用担心; 总而言之就是核心风险可控,我们只需要关注开发连接插件,即可形成产品
我们要基于Liferay + Activiti 做什么
1、账户迁移工具或脚本
如果涉及旧OA或门户迁移,必须开发迁移工具;
2、组织架构迁移工具或脚本
组织架构是非常重要的基础数据;
3、权限组的迁移工具或脚本(非必须)
如果涉及旧OA或门户,权限组最好能批量迁移;
4、Activiti BPM集成Portlet(重点、难度较大)
Liferay自身的BPM较弱,所以需要集成较强Activiti,通过Portlet集成,Portlet其实就是Liferay规范化的servlet 。
5、Activiti BPM集成Liferay权限体系(重点、难度较大)
实现Liferay和Activiti 的权限通讯。
有三个方案:http://www.kafeitu.me/activiti/2012/04/23/synchronize-or-redesign-user-and-role-for-activiti.html
需要慎重选择方案。
6、Activiti-Designer 流程设计器的中国化改造(重点、难度较大)
Activiti 的表单设计器非常强大,但需要改造符合中国企业使用习惯;
Liferay自身的简单BPM估计能实现业务流程需求的简单需求(可能占50%),其余的需要Activiti 实现。
Activiti 中提供了 2 种可视化流程设计器:Web Application 形式的 Activiti Modeler 和 胖客户端形式的流程编辑器Activiti-Designer,必须先慎重选型。
7、开发开放API,方便业务系统调用
这部分可基于Liferay WebAPI扩展包装,难度不大;
Liferay 功能介绍
1、创建站点
管理员可创立多站点。
应对集团多分支的组织架构(按组织分割),或者垂直多用途的门户(按用途分割),比如内外门户、集中知识门户、文档中心等。
配置新站点
可以设置站点的语言、风格、权限等
2、管理页面
创建页面
选择页面的栏式
在页面添加应用
比如添加内置的wiki和最新blog列表应用
配置页面权限
注意:里面的角色(role)是可自行配置的。
创建页面内容:HTML5类型
通过WYSIWYG Web editor,添加文本、图片、视频内容等
创建页面内容:结构化元数据(Metadata )类型
支持的元数据(Metadata )类型有:
- Boolean: true (checked) or false (unchecked)
- Date: 日期
- Decimal: 数字,支持小数点位数;
- Documents and Media: 文档库类型,文档和媒体;
- Geolocation: 地理位置,用于移动端;具体可见https://dev.liferay.com/discover/portal/-/knowledge_base/7-0/geolocating-assets
- HTML: 使用WYSIWYG editor编辑的内容;
- Image: 图片;
- Integer: 整数;
- Link to Page: 链接;
- Number: 类似于Decimal,可能是big decimal,有待确认;
- Radio: 多选按钮;
- Select: 下拉选择按钮;
- Separator: 分隔栏;
- Text: 当行文本;
- Text Box: 多行文本,类似Java控件textArea;
控制页面发布展示和过期时间
scheduling web-content publication
内容搜索
liferay的内容搜索异常强大,不多赘述。
3、管理用户、组织、权限
组织管理
新建组织,并设置上级组织
Type :regular organization or a location,如果选location则没有下级组织
添加用户
用户组
用户组是剥离组织架构的独立逻辑分组,一个用户可以分配给多个用户组。
例如,公司的办公室/部门结构既可以通过组织机构进行建模。也能创建用户组,比如:
- 副总
- 办公室经理
- 会计
- JAVA开发人员
- 博客管理员
- 等等...
一个用户组可以创建一群人独立于他们的组织机构,使它更容易分配一个或多个角色,比如一次性分配权限给所有的JAVA开发人员。 向属于用户组的用户提供预定义的公共或私有页面。例如:
- 博客管理员用户组的成员可以创建管理博客
- 会计用户组的成员可以访问财务页面和应用
- ......
下面是创建(博客管理员)用户组的演示页面:
角色
角色是用来定义一个特定功能的权限(根据特定范围)
一个角色基本上只是一个定义了一个功能的权限集合,如留言板管理员。这有点容易和用户组混淆,但实际上角色还有范围的管辖权限控制
可以细化到4种范围类型选择(Regular role、Site role、Organization role、Team)
导航到控制面板,然后单击“角色”,可以让您创建角色、分配权限给他们,将用户分配给角色。
一个角色仅具有一定管辖范围的作用。比如
-
Site role:一个“企业知识站点留言板管理员”角色只能在一个特定站点(即”企业知识站点“)内管理留言板内容;
-
Team role:一个"CVTalk开发团队"角色只能在一个特定团队站点(即”CVTalk开发“团队)内发布内容;Regular role、Site role、Organization role、Team四种类型的角色之间的范围差异可以描述如下:
-
规则角色:权限在门户级别定义,并在门户级别应用。
-
站点角色:权限被定义在门户级别,并应用到一个特定的站点。
-
组织角色:权限在门户级别定义,并应用于一个特定的组织。
-
团队:权限被定义在一个特定的站点内,并被分配在特定的团队站点内。
用户验证
支持的用户认证方式:
- Liferay自带认证
- LDAP
- SSO
- CAS
- NTLM
- OpenId
- Crowd
- Open SSO
- SiteMinder
- Shibboleth
- SAML
4、文档管理
发布文件上传
发布元数据(Metadata )数据集
就是通过动态字段建立的数据
分布式集群文档存储
Liferay Portal的文件和媒体文件可以存储在许多不同的服务器或其他媒介方式,
默认情况下,Liferay Portal使用文档库中存储的选择被称为简单的文件存储在文件系统上。
您也可以使用一个完全不同的方法来存储文档和媒体文件:
- CMIS存储(Content Management Interoperability Services 内容管理交互服务):使用一个单独的从Liferay存储文件系统。
- DBstore(数据库):使用数据库存储文件。
- JCRstore(Java内容库):将文件分布式存储到兼容JSR-170规范的第三方厂商文档库。商业的有IBM、EMC、SAP、Macromedia的产品,开源的产品也不少,一线的CMS开源基本都符合JSR-170规范,比如Magnolia、eXo、Apache Jackrabbit、Liferay本身也符合,基于Magnolia存储是最优的方案,这样就把Magnolia作为一个用途专一的分布式存储仓库;
- S3store(亚马逊简单存储):使用亚马逊的云存储解决方案。
- 其他定制方案:这需要您自行实现Liferay开发接口,实现更大的灵活性;
文件同步客户端
有些类似百度云客户端,在授权的情况下,可以把文档库同步到个人电脑硬盘。
详情:Using Liferay Sync on Your Desktop
移动端访问文档库
移动端编辑文件
5、企业协作
博客
论坛
用户心情
Wiki
略
书签
略
企业微博
通知
投票
集成XMPP Web聊天
通过集成jabber方式和openfire通讯
集成Email
Liferay包含一个邮件插件,可以作为web邮件客户端
5、管理应用
Liferay的强大之处在于不仅内置应用繁多,它还提供易于开发的扩展体系,提供即插即用的平台支持,海量应用商店支持。
管理应用
应用商店
应用商店有数千个免费或商业插件
应用的类别:
- OSGi Modules
- Portlets
- Web Plugins
- Templates
- Themes
以上内容为官方文档的微缩版:The Liferay Distinction
6、企业功能
涉及用户数据列表、表单模板、高级表单定制、工作流。
放在下一篇介绍。
多谢观看!
Liferay-Activiti 企业特性功能介绍(新版Liferay7)
前言
如果你是开发者
你已经是多少次开发一个项目,一次次的用一些框架,一次次的写类似的重复的代码,一次次建表\写类和方法\写HTML\CSS\JAVASCRIPT,一次次测试,一次次的写Bug。。。
如果有一个平台,提供基线的框架,可以是应用程序\网站,支持移动端,不必一次次开发无法重用,一次次造就信息孤岛。
那么试试Liferay。 很多的应用开箱即用,如CMS\博客\企业协作\动态表单\良好的组织架构和权限体系。
先别急着上船,你得接受和适应Liferay的扩展框架体系,比如Portlet,还有应用程序显示模版的机制,这有些代价,但对于真正的JavaEE开发者,并不会困难,另外你还会学到另你获益终身的设计模式思想,OK,想好了就上船吧,这必将是愉快的旅程。
如果你是IT主管
可能选型选择开源不容易,哪怕是Liferay如此成功的产品,实际上Liferay的企业服务费用不算低,要节省成本用社区版,必然要有好的开发服务团队。
这必须非常谨慎,诚然,Liferay还有软肋,这个是其产品定位造成的,比如:
- BPM进来,如Activiti;
- 还有中国式的组织架构;
- 项目数据迁移,比如员工,组织架构数据;
- 和其他系统的集成;
- 还有一些中国式的操作习惯改造;
- 国内的甲方公司很难有这样的开发实力,这需要技术和耐力相结合,虽然只是开发连接器插件,但做好并不容易,开发技能栈必须全面。
言归正传,介绍Liferay的企业特性。
Liferay7架构 :
模块:
权限和认证体系
Liferay支持 权限、组、用户、团队、组织架构 的权限控制体系,详情见上一篇文章
支持的用户认证方式:
- Liferay自带认证
- LDAP
- SSO
- CAS
- NTLM
- OpenId
- Crowd
- Open SSO
- SiteMinder
- Shibboleth
- SAML
开发扩展方式
Liferay几乎什么都可以定制。应用程序接口可以重新设计,整个用户界面可以定制为主题,菜单项可以添加或删除...
所有的应用程序及扩展,是建立在liferay自定义分布式部署模块(典型的jar文件),用Java开发人员熟悉的方式开发,编译,定义模板,资源,和一些元数据。
它遵循一套非常强大的标准称为OSGi。多模块可以相互依赖、相互沟通,实时部署,不用重启服务器(热部署)。
模块可以有一个或多个组件。创建一个组件和Java类开发一样简单。
一个组件是一个更大的应用程序的最小的构建块,并且应用程序本身是由许多小的组件组成的,就是以重复使用的堆积木的方式开发系统。
组件由组件容器管理,该容器提供安装和激活。组件提供服务,通过一个强大的依赖管理系统,在运行时自动处理。
您可以编写组件以提供新的服务或以重写现有的服务,容器管理所有一切。Liferay是一个激动人心的平台,使开发人员更高效。
扩展方式介绍:
- OSGi Modules
- 建立Liferay Portal Web应用程序最常用的方法是用一个portlet;
- 利用Liferay的移动SDK,开发移动应用;
- 开发主题(theme);
- 开发MVCPortlet
可以使用现有框架,如Struts、Spring。使用Service Builder,很容易创建后台数据库表、对象关系映射。
它还可以生成JSON或SOAP的Web服务,为开发者提供完整的开发元素用于存储和检索数据,用于Web或移动客户端。
工作流
Liferay自身支持的BPM工作流:
-
Kaleo,集成Liferay表单的内置工作流,极简单,没有图形设计器;
-
jBPM3, 现在都6.4版了,有点跟不上时代;
另外还有两个外接集成插件:
集成Activiti5.11版:https://github.com/emdev-limited/activiti-liferay ,功能还不够完善,已经3年没有更新;
集成Bonita BPM:http://www.bonitalife.org ,Bonita 也是一款强大的BPM,这个插件还待试用评;
Activiti是完全实现BPMN 2.0规范的工作流引擎,它对比jBPM的优势是轻量级,容易集成,可单机或集群部署。
目前Activiti支持的数据库:
- DB2
- H2
- Oracle
- MySQL
- MS SQL
- PostgreSQL
Activiti的集成开发方式有:
- Standalone JDBC :通过Portlet方式,加入Activiti的jar包,直接集成;
- Spring:通过Spring MVC方式集成;
- JTA:对J2EE异构分布式数据源的集成开发;
- Web API:通过restful接口进行集成;
所有的开发重点在UI的集成、用户权限的集成、单点登录、流程设计器、控制面板、和Liferay表单的集成,待办已办待阅面板开发,工程较大。
目前Activiti只有用户组、用户上级、用户三种权限概念(用于流程节点分配);
扩展更复杂更集成业务系统的权限机制的方案: synchronize-or-redesign-user-and-role-for-activiti
一些企业扩展
企业社交代理
通过整合OAuth服务作为一个HTTP代理服务器的应用程序,该插件为您的应用程序提供了一个安全的令牌,可以将类似的网站推特,LinkedIn、微信;
图表插件
一个使用 Liferay 和 Lucene 实现企业门户智能帮助机器人的方法
很有意思
使用 Liferay 和 Lucene 实现企业门户智能帮助机器人
企业门户智能帮助机器人总体架构图
简易敏捷插件
用户反馈插件
Contour Dispatch – The Feedback Portlet
相册插件
MongoDB CRUD 简单应用插件
features.
- Insert New Record (in Collection/Table).
- Update inserted records.
- Delete Single/Multiple Record(s).
- Sorting (Ascending/Descending)
- Searching (AND search / OR search)
Liferay MongoDB CRUD Application
集成导入插件
使用这个Web服务插件,你可以从其他平台导入内容到Liferay中
更多插件
在应用商店:Marketplace
选择Liferay的好处是除了自带的强大功能,和海量商店应用,它还是可以灵活开发扩展的平台。
比较Activiti中三种不同的表单及其应用
开篇语
这个恐怕是初次接触工作流最多的话题之一了,当然这个不是针对Activiti来说的,每个工作流引擎都会支持多种方式的表单。目前大家讨论到的大概有三种。
- 动态表单
- 外置表单
- 普通表单
具体选择哪种方式只能读者根据自己项目的实际需求结合现有技术或者架构、平台选择!!!
1.动态表单
这是程序员 最喜欢 的方式,同时也是客户 最讨厌 的……因为表单完全没有布局,所有的表单元素都是顺序输出显示在页面。
此方式需要在流程定义文件( bpmn20.xml )中用 activiti:formProperty 属性定义,可以在开始事件( Start Event )和 Task 上设置,而且支持变量自动替换,语法就是 UEL 。
<startevent id="startevent1" name="Start">
<extensionelements>
<activiti:formproperty id="name" name="Name" type="string"></activiti:formproperty>
</extensionelements>
</startevent>
<usertask id="usertask1" name="First Step">
<extensionelements>
<activiti:formproperty id="setInFirstStep" name="SetInFirstStep" type="date"></activiti:formproperty>
</extensionelements>
</usertask>
下面是一个简单的动态表单的单元测试,读者可以下载运行以便更明确执行过程和判断动态表单能不能在企业项目中使用。
- DymaticForm.bpmn
- ProcessTestDymaticForm.java
下载之后复制到eclipse工程里,更改里面的路径配置使用JUnit测试即可。
当流程需要一些特殊处理时可以借助Listener或者Delegate方式实现。
注意:表单的内容都是以key和value的形式数据保存在引擎表中!!!
2.外置表单
这种方式常用于基于工作流平台开发的方式,代码写的很少,开发人员只要把表单内容写好保存到 .form 文件中即可,然后配置每个节点需要的表单名称( form key ),实际运行时通过引擎提供的 API 读取 Task 对应的 form 内容输出到页面。
此种方式对于在经常添加新流程的需求比较适用,可以快速发布新流程,把流程设计出来之后再设计表单之后两者关联就可以使用了。例如公司内部各种简单的审批流程,没有业务逻辑处理,仅仅是多级审批是否通过等等情况
当流程需要一些特殊处理时可以借助 Listener 或者 Delegate 方式实现。
Activiti Explorer 就是使用的这种方式,表单信息都配置在流程定义文件中。
代码片段:
<process id="FormKey" name="FormKey">
<startevent id="startevent1" name="Start" activiti:formkey="diagrams/form/start.form"></startevent>
…
</process>
同样也提供了单元测试:
- FormKey.bpmn20.xml
- start.form
- first-step.form
- ProcessTestFormKey.java
注意:表单的内容都是以key和value的形式数据保存在引擎表中!!!
3.普通表单
这个是最灵活的一种方式,常用于业务比较复杂的系统中,或者业务比较固定不变的需求中,例如ERP系统。
普通表单的特点是把表单的内容存放在一个页面(jsp、jsf、html等)文件中,存放方式也有两种(一体式、分离式):
-
一体式:把整个流程涉及到的表单放在一个文件然后根据处理的任务名称匹配显示,kft-activiti-demo的普通表单模式就是一体式的做法,把表单内容封装在一个div里面,div的ID以节点的名称命名,点击“办理”按钮时用对话框的方式把div的内容显示给用户。
-
分离式:对于非Ajax应用来说比较常用,每个任务对应一个页面文件,点击办理的时候根据任务的ID动态指定表单页面。
本博客发布的Activiti入门Demo中有演示: Activiti快速入门项目-kft-activiti-demo
和以上两种方式比较有两点区别:
- 表单:和第二种外置表单类似,但是表单的显示、表单字段值填充均由开发人员写代码实现。
- 数据表:数据表单独设计而不是和前两种一样把数据以key、value形式保存在引擎表中。
4.从业务数据和流程关联比较
- 动态表单:引擎已经自动绑定在一起了,不需要额外配置。
- 外置表单:和业务关联是可选的,提供的例子中是没有和业务关联的,如果需要关联只需要在提交StartForm的时候设置businessKey即可。
- 普通表单:这个应该是必须和业务关联,否则就是无头苍蝇了……,关联方式可以参考:工作流引擎Activiti使用总结中的2.3 业务和流程的关联方式
5.结束语
技术只是辅助工具,只能决定这件事能不能做,如何选择要看应用场合,希望简单的比较能提供一点思路。
[Activiti 6.x] Springboot 1.5x MySQL
Activiti 6.x 开篇 Springboot 1.5x + Activiti6.0 + MySQL 整合
创建项目
使用 STS 以java8创建项目
选择springboot 1.x的最新版本,activiti的starter是基于springboot1.x做的,所以这里采用1.x,不需要使用web,引入mysql即可。
整合配置
1.加入配置
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter-basic</artifactId>
<version>6.0.0</version>
</dependency>
2.按照官网上的说明加入@EnableAutofiguration注解如下【官网上另外两个注解可以不加】
3.运行项目报错,activiti需要配置数据库
4.数据库配置,这里使用druid连接池与mysql数据库【默认使用H2内存数据库。QAQ嗯重新启动程序数据就没了】
配置如下,你懂的。顺手配一下日志
5.processes配置
one-task-process.bpmn20.xml 【来自官网】
<?xml version="1.0" encoding="UTF-8"?>
<definitions
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn"
targetNamespace="Examples">
<process id="oneTaskProcess" name="The One Task Process">
<startEvent id="theStart" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="theTask" />
<userTask id="theTask" name="my task" />
<sequenceFlow id="flow2" sourceRef="theTask" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
</definitions>
6.配置完成
完成配置自动生成28张表
[Activiti 6.x] 基本流程讲解与开发前奏
Activiti 6.x 基本流程讲解与开发前奏
核心API介绍
springboot环境下不再以activiti.cfg.xml文件的形式配置,可在yml内配置,yml配置会在后文讲解。
RepositoryService:对流程定义进行管理。
RuntimeService:对流程实例的管理。
TaskService:对流程任务进行管理。
IdentityService:管理用户和用户组。
ManagementService:提供对activiti数据库的直接访问【一般不用】。
HistoryService:对流程的历史数据进行操作。
FormService:动态表单。
Activiti6 YML配置
1.加入配置
spring:
activiti:
database-schema-update: false
check-process-definitions: false
activiti使用starter配置后属于spring下。
check-process-definitions【检查Activiti数据表是否存在及版本号是否匹配】默认为true,自动创建好表之后设为false。设为false会取消自动部署功能。
database-schema-update【在流程引擎启动和关闭时处理数据库模式】如下(摘自官网)
- false (默认值):在创建流程引擎时检查库模式的版本,如果版本不匹配则抛出异常。
- true:在创建流程引擎时,执行检查并在必要时对数据库中所有的表进行更新,如果表不存在,则自动创建。
- create-drop:在创建流程引擎时,会创建数据库的表,并在关闭流程引擎时删除数据库的表。
准备步骤
1.测试项目结构:主要为做工作流部署的两种形式,zip为bpmn与png文件的压缩文件
2.安装eclipse activiti插件
3.流程图如下
代码后续用到功能时会贴出
RepositoryService
打开测试类
部署流程定义
/**部署流程定义*/
@Test
public void deploy(){
Deployment deployment = repositoryService.createDeployment()//创建一个部署对象
.name("helloworld入门程序")
.addClasspathResource("bpmn/MyProcess.bpmn")//从classpath的资源中加载,一次只能加载一个文件
.addClasspathResource("bpmn/MyProcess.png")
.deploy();
System.out.println("部署ID:"+deployment.getId());
System.out.println("部署名称:"+deployment.getName());
}
部署过程同时会影响三张表的数据
ACT_RE_DEPLOYMENT(第二行)
ACT_GE_BYTEARRAY(文件会被存在这张表内,activiti以纵表方式存储数据)
ACT_RE_PROCDEF
部署流程定义(zip)
/**部署流程定义(zip) */
@Test
public void deployzip() throws IOException{
InputStream in = this.getClass().getClassLoader().getResourceAsStream("bpmn/MyProcess.zip");
ZipInputStream zipInputStream = new ZipInputStream(in);
Deployment deployment = repositoryService.createDeployment()
.name("helloworld入门程序2")
.addZipInputStream(zipInputStream)//指定zip格式的文件完成部署
.deploy();//完成部署
System.out.println("部署ID:"+deployment.getId());
System.out.println("部署名称:"+deployment.getName());
zipInputStream.close();
}
【PS:流程实例可以重复部署,有版本号作为标识】
删除流程定义
/**删除流程定义*/
@Test
public void deleteProcess(){
String deploymentId = "25001";
/**不带级联的删除:只能删除没有启动的流程,如果流程启动,就会抛出异常*/
// repositoryService.deleteDeployment(deploymentId);
/**级联删除:不管流程是否启动,都能可以删除(emmm大概是一锅端)*/
repositoryService.deleteDeployment(deploymentId, true);
System.out.println("删除成功!");
}
RuntimeService
【多个流程实例之间不会相互影响】
注入Service
启动流程实例
/**启动流程实例*/
@Test
public void startProcessInstance(){
//流程定义的key
String processDefinitionKey = "myProcess";
//key对应MyProcess.bpmn文件中id的属性值,使用key值启动,默认是按照最新版本的流程定义启动
ProcessInstance pi = runtimeService.startProcessInstanceByKey(processDefinitionKey);
System.out.println("流程实例ID:"+pi.getId());//流程实例ID
System.out.println("流程定义ID:"+pi.getProcessDefinitionId());//流程定义ID
}
查询流程实例
/**查询流程实例*/
@Test
public void searchProcessInstance(){
String processDefinitionKey = "myProcess";
ProcessInstance pi = runtimeService.createProcessInstanceQuery()
.processDefinitionKey(processDefinitionKey)
.singleResult();
System.out.println("流程实例ID:"+pi.getId());
System.out.println("流程定义ID:"+pi.getProcessDefinitionId());
}
流程实例的删除
/**流程实例的删除*/
@Test
public void deleteProcessInstanceTest(){
String processDefinitionKey = "myProcess";
ProcessInstance pi = runtimeService.createProcessInstanceQuery()
.processDefinitionKey(processDefinitionKey)
.singleResult();
String processInstanceId = pi.getProcessInstanceId();
System.out.println("流程实例ID:"+pi.getId());
runtimeService.deleteProcessInstance(processInstanceId,"删除测试");
}
[Activiti 6.x] 核心API基础
Activiti 6.x 继续讲解核心API基础
核心API介绍
springboot环境下不再以activiti.cfg.xml文件的形式配置,可在yml内配置,yml配置会在后文讲解。
RepositoryService:对流程定义进行管理。
RuntimeService:对流程实例的管理。
TaskService:对流程任务进行管理。
IdentityService:管理用户和用户组。
ManagementService:提供对activiti数据库的直接访问【一般不用】。
HistoryService:对流程的历史数据进行操作。
FormService:动态表单。
IdentityService
用户管理
先讲IdentityService,后续联合TaskService、RuntimeService实现并行网关demo。 新建用户【无法创建两个ID一样的用户】
/**新建用户*/
@Test
public void testUser(){
User user = identityService.newUser("ptm");
user.setFirstName("潘");
user.setLastName("天淼");
user.setEmail("1458689676@qq.com");
user.setPassword("123456");
//新建用户
identityService.saveUser(user);
}
一般用户信息图片啥的就别存在activiti里面了,自带的表应该是满足不了真实业务需求的。呐QAQ你看下面这表。建议的话是在identityService的基础上,以userid与新建的表关联起来。RBAC了解一下。
获取用户信息【更新用户信息请先获取用户信息对获取的user,set属性调用saveUser方法即可】
/**获取用户信息*/
@Test
public void searchUser(){
String userid ="ptm";
User user = identityService.createUserQuery()
.userId(userid).singleResult();
System.out.println("博主的名字:"+user.getFirstName()+user.getLastName());
System.out.println("博主的邮箱:"+user.getEmail());
System.out.println("呐-ID肯定是相等的:"+userid.equals(user.getId()));
System.out.println("测试密码:"+user.getPassword());
System.out.println("是否验证成功:"+identityService.checkPassword("ptm","123456"));
}
删除用户信息
/**删除用户信息*/
@Test
public void delUser(){
identityService.deleteUser("ptm");
}
如果需要用activiti的用户表,接口有如下方法
组管理
/**用户组管理*/
@Test
public void testGroup(){
String groupId ="HRPTM";
//创建用户组对象
Group group = identityService.newGroup(groupId);
group.setName("HR");
group.setType("assignment");
//保存用户组
identityService.saveGroup(group);
Group groupInfo = identityService.createGroupQuery()
.groupId(groupId)
.singleResult();
System.out.println("组的名字:"+groupInfo.getName());
System.out.println("组类别"+groupInfo.getType());
System.out.println("GroupId:"+groupInfo.getId());
// //删除用户组
// identityService.deleteGroup("HRPTM");
}
组的管理与用户管理类似,就直接一回杀了。执行结果如下:
Membership管理(用户对应组关系)
/**Membership管理*/
@Test
public void testMembership(){
//建立关联
identityService.createMembership("ptm","HRPTM");
//查询属于HRPTM用户组的用户
User user = identityService.createUserQuery()
.memberOfGroup("HRPTM")
.singleResult();
System.out.println("博主的名字:"+user.getFirstName()+user.getLastName());
System.out.println("博主的邮箱:"+user.getEmail());
System.out.println("userid"+user.getId());
System.out.println("测试密码:"+user.getPassword());
System.out.println("是否验证成功:"+identityService.checkPassword("ptm","123456"));
//查询用户所属组
Group group = identityService.createGroupQuery()
.groupMember("ptm")
.singleResult();
System.out.println("组的名字:"+group.getName());
System.out.println("组类别"+group.getType());
System.out.println("GroupId:"+group.getId());
}
另外测试的key用的都是普通的英文,如是正式项目建议UUID。
[Activiti 6.x] 基础流程DEMO
Activiti 6.x 基础流程学习
流程图介绍:
流程图总览
网关condition设置
组设置
用户设置
【一般以流程变量形式设置办理人(把请假理解成一种任务,办理人即请假人)】
流程图代码
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="EmployeeAskForLeave">
<process id="myProcess" name="员工请假流程" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
<userTask id="usertask1" name="填写请假申请" activiti:assignee="${userKey}"></userTask>
<exclusiveGateway id="exclusivegateway1" name="请假时间判断(排他网关)"></exclusiveGateway>
<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="exclusivegateway1"></sequenceFlow>
<userTask id="usertask2" name="经理审批" activiti:candidateGroups="HR"></userTask>
<userTask id="usertask3" name="总监审批" activiti:candidateGroups="ZJ"></userTask>
<sequenceFlow id="flow3" name="小于等于3天" sourceRef="exclusivegateway1" targetRef="usertask2">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${days<=3}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow4" name="大于3天" sourceRef="exclusivegateway1" targetRef="usertask3">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${days>3}]]></conditionExpression>
</sequenceFlow>
<exclusiveGateway id="exclusivegateway2" name="请假时间判断(排他网关)"></exclusiveGateway>
<sequenceFlow id="flow5" sourceRef="usertask2" targetRef="exclusivegateway2"></sequenceFlow>
<sequenceFlow id="flow6" sourceRef="usertask3" targetRef="exclusivegateway2"></sequenceFlow>
<sequenceFlow id="flow7" sourceRef="exclusivegateway2" targetRef="endevent1"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_myProcess">
<bpmndi:BPMNPlane bpmnElement="myProcess" id="BPMNPlane_myProcess">
<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
<omgdc:Bounds height="35.0" width="35.0" x="200.0" y="220.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
<omgdc:Bounds height="35.0" width="35.0" x="1040.0" y="220.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
<omgdc:Bounds height="55.0" width="105.0" x="320.0" y="210.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="exclusivegateway1" id="BPMNShape_exclusivegateway1">
<omgdc:Bounds height="40.0" width="40.0" x="470.0" y="217.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask2" id="BPMNShape_usertask2">
<omgdc:Bounds height="55.0" width="105.0" x="650.0" y="130.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask3" id="BPMNShape_usertask3">
<omgdc:Bounds height="55.0" width="105.0" x="650.0" y="270.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="exclusivegateway2" id="BPMNShape_exclusivegateway2">
<omgdc:Bounds height="40.0" width="40.0" x="920.0" y="217.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
<omgdi:waypoint x="235.0" y="237.0"></omgdi:waypoint>
<omgdi:waypoint x="320.0" y="237.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
<omgdi:waypoint x="425.0" y="237.0"></omgdi:waypoint>
<omgdi:waypoint x="470.0" y="237.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
<omgdi:waypoint x="490.0" y="217.0"></omgdi:waypoint>
<omgdi:waypoint x="490.0" y="157.0"></omgdi:waypoint>
<omgdi:waypoint x="650.0" y="157.0"></omgdi:waypoint>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="14.0" width="66.0" x="490.0" y="198.0"></omgdc:Bounds>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
<omgdi:waypoint x="490.0" y="257.0"></omgdi:waypoint>
<omgdi:waypoint x="490.0" y="297.0"></omgdi:waypoint>
<omgdi:waypoint x="650.0" y="297.0"></omgdi:waypoint>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="14.0" width="42.0" x="490.0" y="257.0"></omgdc:Bounds>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5">
<omgdi:waypoint x="755.0" y="157.0"></omgdi:waypoint>
<omgdi:waypoint x="940.0" y="157.0"></omgdi:waypoint>
<omgdi:waypoint x="940.0" y="217.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow6" id="BPMNEdge_flow6">
<omgdi:waypoint x="755.0" y="297.0"></omgdi:waypoint>
<omgdi:waypoint x="940.0" y="297.0"></omgdi:waypoint>
<omgdi:waypoint x="940.0" y="257.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow7" id="BPMNEdge_flow7">
<omgdi:waypoint x="960.0" y="237.0"></omgdi:waypoint>
<omgdi:waypoint x="1040.0" y="237.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
数据准备
//部署流程定义,新建数据
@Test
public void prepare() {
Deployment deployment = repositoryService.createDeployment()//创建一个部署对象
.name("请假流程")
.addClasspathResource("bpmn/MyProcess.bpmn")
.addClasspathResource("bpmn/MyProcess.png")
.deploy();
System.out.println("部署ID:"+deployment.getId());
System.out.println("部署名称:"+deployment.getName());
Group group1 = identityService.newGroup("HR");
group1.setName("HR");
group1.setType("HRassignment");
identityService.saveGroup(group1);//建立HR组
Group group2 = identityService.newGroup("ZJ");
group2.setName("ZJ");
group2.setType("ZJassignment");
identityService.saveGroup(group2);//建立ZJ组
Group group3 = identityService.newGroup("EP");
group3.setName("EP");
group3.setType("EPassignment");
identityService.saveGroup(group3);//建立员工组
//newUser传的是key【不是名字】
identityService.saveUser(identityService.newUser("HR1"));//高管
identityService.saveUser(identityService.newUser("HR2"));//高管
identityService.saveUser(identityService.newUser("ZJ"));//总监
identityService.saveUser(identityService.newUser("ZJ2"));//总监
identityService.saveUser(identityService.newUser("PTM"));//员工
identityService.createMembership("HR1", "HR");
identityService.createMembership("HR2", "HR");
identityService.createMembership("ZJ", "ZJ");
identityService.createMembership("ZJ2", "ZJ");
identityService.createMembership("PTM", "EP");
}
启动流程设置流程变量【流程变量必须指定不然报错】
/**启动流程实例分配任务给个人*/
@Test
public void start() {
String userKey="PTM";//脑补一下这个是从前台传过来的数据
String processDefinitionKey ="myProcess";//每一个流程有对应的一个key这个是某一个流程内固定的写在bpmn内的
HashMap<String, Object> variables=new HashMap<>();
variables.put("userKey", userKey);//userKey在上文的流程变量中指定了
ProcessInstance instance=runtimeService
.startProcessInstanceByKey(processDefinitionKey,variables);
System.out.println("流程实例ID:"+instance.getId());
System.out.println("流程定义ID:"+instance.getProcessDefinitionId());
}
运行结果:
【通过流程变量指定User的结果】
TaskService
查询当前人的个人任务
/**查询当前人的个人任务*/
@Test
public void findTask(){
String assignee = "PTM";
List<Task> list = taskService.createTaskQuery()//创建任务查询对象
.taskAssignee(assignee)//指定个人任务查询
.list();
if(list!=null && list.size()>0){
for(Task task:list){
System.out.println("任务ID:"+task.getId());
System.out.println("任务名称:"+task.getName());
System.out.println("任务的创建时间:"+task.getCreateTime());
System.out.println("任务的办理人:"+task.getAssignee());
System.out.println("流程实例ID:"+task.getProcessInstanceId());
System.out.println("执行对象ID:"+task.getExecutionId());
System.out.println("流程定义ID:"+task.getProcessDefinitionId());
}
}
}
Query里面可以写多个查询条件类是EF的那种
完成任务
/**完成任务*/
@Test
public void completeTask(){
//任务ID
String taskId = "47506";
HashMap<String, Object> variables=new HashMap<>();
variables.put("days", 4);//userKey在上文的流程变量中指定了
taskService.complete(taskId,variables);
System.out.println("完成任务:任务ID:"+taskId);
}
组任务查询【因为days=4所以由总监组负责】
/**查询当前人的组任务*/
@Test
public void findTaskGroup(){
//String assignee = "PTM";
List<Task> list = taskService.createTaskQuery()//创建任务查询对象
.taskCandidateUser("ZJ")//指定组任务查询
.list();
String taskid ="";
String instanceId ="";
if(list!=null && list.size()>0){
for(Task task:list){
System.out.println("任务ID:"+task.getId());
System.out.println("任务名称:"+task.getName());
System.out.println("任务的创建时间:"+task.getCreateTime());
System.out.println("任务的办理人:"+task.getAssignee());
System.out.println("流程实例ID:"+task.getProcessInstanceId());
System.out.println("执行对象ID:"+task.getExecutionId());
System.out.println("流程定义ID:"+task.getProcessDefinitionId());
taskid=task.getId();
instanceId = task.getProcessInstanceId();
}
}
//查询组任务成员[两种方式]
//List<IdentityLink> listIdentity = taskService.getIdentityLinksForTask(taskid);
List<IdentityLink> listIdentity = runtimeService.getIdentityLinksForProcessInstance(instanceId);
for(IdentityLink identityLink:listIdentity ){
System.out.println("userId="+identityLink.getUserId());
System.out.println("taskId="+identityLink.getTaskId());
System.out.println("piId="+identityLink.getProcessInstanceId());
}
}
//taskService.claim(taskid,"ZJ2");//指定办理人
//taskService.setAssignee(taskid, null);//回退为组任务状态
指定办理人后会变为用户任务。
/**查询当前人的组任务*/
@Test
public void findTaskGroup(){
String assignee = "ZJ2";
List<Task> list = taskService.createTaskQuery()//创建任务查询对象
//.taskCandidateUser("ZJ")//指定组任务查询
.taskAssignee(assignee)//指定个人任务查询
.list();
String taskid ="";
String instanceId ="";
if(list!=null && list.size()>0){
for(Task task:list){
System.out.println("任务ID:"+task.getId());
System.out.println("任务名称:"+task.getName());
System.out.println("任务的创建时间:"+task.getCreateTime());
System.out.println("任务的办理人:"+task.getAssignee());
System.out.println("流程实例ID:"+task.getProcessInstanceId());
System.out.println("执行对象ID:"+task.getExecutionId());
System.out.println("流程定义ID:"+task.getProcessDefinitionId());
taskid=task.getId();
instanceId = task.getProcessInstanceId();
}
}
//taskService.claim(taskid,"ZJ2");//指定办理人
taskService.setAssignee(taskid, null);//回退为组任务状态
List<IdentityLink> listIdentity = taskService.getIdentityLinksForTask(taskid);
//List<IdentityLink> listIdentity = runtimeService.getIdentityLinksForProcessInstance(instanceId);
//runtime查询没有taskId,task查询没有InstanceId
for(IdentityLink identityLink:listIdentity ){
System.out.println("userId="+identityLink.getUserId());
System.out.println("taskId="+identityLink.getTaskId());
System.out.println("piId="+identityLink.getProcessInstanceId());
}
}
代码里退回为组任务【即无办理人】
【流程就结束了。记录会被移到history表里面去。】
[Activiti 6.x] 剩下的核心API
Activiti 6.x 剩下的核心API讲解
HistoryService
流程定义与流程实例
/**HistoryProcessInstance*/
@Test
public void HistoryProcessInstance() {
List<HistoricProcessInstance> datas = historyService.createHistoricProcessInstanceQuery()
.finished().list();
for (HistoricProcessInstance historicProcessInstance : datas) {
System.out.println("流程实例id: "+historicProcessInstance.getId());
System.out.println("部署id: "+historicProcessInstance.getDeploymentId());
System.out.println("结束event: "+historicProcessInstance.getEndActivityId());
System.out.println("流程名称: "+historicProcessInstance.getName());
System.out.println("PROC_DEF_ID: "+historicProcessInstance.getProcessDefinitionId());
System.out.println("流程定义Key: "+historicProcessInstance.getProcessDefinitionKey());
System.out.println("流程定义名称: "+historicProcessInstance.getProcessDefinitionName());
System.out.println("开始event: "+historicProcessInstance.getStartActivityId());
}
}
【其他HistoryService查询类似】
FormService
【个人感觉在前端框架比较完善的今天几乎不会去用这个,但是还是做一些小demo】
activiti:formProperty【动态表单】
【流程图】
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
<process id="whatFk" name="whatFk" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="User Task">
<extensionElements>
<activiti:formProperty id="userName" name="userName" type="string" variable="userName"></activiti:formProperty>
<activiti:formProperty id="age" name="age" type="string" variable="age"></activiti:formProperty>
</extensionElements>
</userTask>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
<userTask id="usertask2" name="User Task"></userTask>
<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
<sequenceFlow id="flow3" sourceRef="usertask2" targetRef="endevent1"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_whatFk">
<bpmndi:BPMNPlane bpmnElement="whatFk" id="BPMNPlane_whatFk">
<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
<omgdc:Bounds height="35.0" width="35.0" x="210.0" y="130.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
<omgdc:Bounds height="55.0" width="105.0" x="310.0" y="120.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
<omgdc:Bounds height="35.0" width="35.0" x="840.0" y="130.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask2" id="BPMNShape_usertask2">
<omgdc:Bounds height="55.0" width="105.0" x="560.0" y="120.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
<omgdi:waypoint x="245.0" y="147.0"></omgdi:waypoint>
<omgdi:waypoint x="310.0" y="147.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
<omgdi:waypoint x="415.0" y="147.0"></omgdi:waypoint>
<omgdi:waypoint x="560.0" y="147.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
<omgdi:waypoint x="665.0" y="147.0"></omgdi:waypoint>
<omgdi:waypoint x="840.0" y="147.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
@Test
public void FormType(){
Deployment dep = repositoryService.createDeployment().addClasspathResource("bpmn/form.bpmn").deploy();
ProcessInstance pi = runtimeService.startProcessInstanceByKey("whatFk");
System.out.println(pi);
HashMap<String, String> variables = new HashMap<>();
variables.put("userName", "潘天淼");
variables.put("age", "18");
Task task = taskService.createTaskQuery().deploymentId(dep.getId()).singleResult();
formService.submitTaskFormData(task.getId(), variables);
}
数据库内以流程变量的形式存储了内容【注意:此方式设置的流程变量均为taskID】
@Test
public void FormTypeRead(){
//75005为偷懒直接从数据库内复制的流程
Task task = taskService.createTaskQuery().processInstanceId("75005").singleResult();
TaskFormData taskFormData = formService.getTaskFormData(task.getId());
List<FormProperty> formProperties = taskFormData.getFormProperties();
for (FormProperty formProperty : formProperties) {
System.out.println(formProperty.getId() + ",value=" + formProperty.getValue());
}
}
上方代码在该task未执行时能够获取表单所有元素
activiti:formKey【外置表单】
【修改代码流程文件与form要一同部署】
@Test
public void FormType(){
Deployment dep = repositoryService.createDeployment()
.addClasspathResource("bpmn/form.bpmn")
.addClasspathResource("bpmn/completeForm.form")
.deploy();
ProcessInstance pi = runtimeService.startProcessInstanceByKey("whatFk");
HashMap<String, String> variables = new HashMap<>();
variables.put("userName", "潘天淼");
variables.put("age", "18");
Task task = taskService.createTaskQuery().deploymentId(dep.getId()).singleResult();
formService.submitTaskFormData(task.getId(), variables);
}
【测试结果】
@Test
public void OutFormTypeRead(){
System.out.println(formService.getRenderedTaskForm("105016")==null);
HashMap<String, String> variables = new HashMap<>();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Calendar ca = Calendar.getInstance();
String startDate = sdf.format(ca.getTime());
ca.add(Calendar.DAY_OF_MONTH, 2); // 当前日期加2天
String endDate = sdf.format(ca.getTime());
variables.put("startDate", startDate);
variables.put("endDate", endDate);
variables.put("reason", "公休");
formService.submitTaskFormData("105016", variables);
}
正常情况下呢这里是可以获取表单的。对于外置表单,只是把表单内容都保存在单独的form文件中,所以只能通过读取所有的请求参数作为流程启动参数。
图示部分为整块HTML的内容【想些啥都OK我是复制了一段HTML带表单的说】。
小结
activiti 基础部分到此就结束了。关于managementService据说是不怎么用的到的,呐网上资源也比较少,就不去细说了。接下来的话。本周内会把BPMN2.0涉及到的组件全部过一遍【不常用的Task组件我就不讲了。中间组件和补偿边界会全部涉及】。




































































































