一、概述
- activiti是一个工作流引擎,可以将业务系统中复杂的业务流程抽取出来,使用专门的建模语言BPMN进行定义,业务流程按照预先定义的流程进行执行。实现了系统的流程由activiti进行管理,减少业务系统由于流程变更进行系统升级改造的工作流量,从而提高系统的健壮性,同时也减少了系统开发维护成本。
- 工作流(Workflow),就是通过计算机对业务流程自动化执行管理。它主要解决的是“使在多个参与者之间按照某种预定义的规则自动进行传递文档、信息或任务的过程,从而实现某个预期的业务目标,或者促使此目标的实现”。通俗来讲,就是业务上一个完整的审批流程。例如员工的请假,出差,外出采购,合同审核等等,这些过程,都是一个工作流。
- BPMN(建模语言):BPM(Business Process Management)即业务流程管理,是一种规范化的构造端到端的业务流程,以持续提高组织业务效率。BPM 软件就是根据企业中业务环境的变化,推进人与人之间、人与系统之间以及系统与系统之间的整理及调整的经营方法与解决方案的 IT 工具。使用 BPM 软件对企业内部及外部的业务流程的整个生命周期进行建模、自动化、管理监控和优化,可以降低企业成本,提高利润。BPMN(Business Process Model AndNotation)即业务流程模型和符号,是一套标准的业务流程建模符号,使用 BPMN 提供的符号可以创建业务流程。Activit 就是使用 BPMN 进行流程建模、流程执行管理的。
- BPMN基本符号:
- 事件Event:开始事件(一个流程的开始)、中间事件(在开始事件和结束事件之间的事件)、结束事件(表示流程的结束)。
- 活动Activities:活动可以是一个任务,还可以是当前流程的子流程。活动有不同类型,如:用户任务、服务任务和子流程等。
- 网关Gateway:用于表示流程的分支与合并。
- 流向Flow:流是连接流程中两个节点的连线,常见的流向有:顺序流、信息流和关联等。
二、Activiti使用流程
- 引入依赖并初始化数据库;
- 通过工具绘制流程图;
- 流程定义部署;
- 启动一个流程实例;
- 用户查询待办任务;
- 用户办理任务;
- 流程结束。
三、Activiti入门使用
3.1 引入Activiti依赖
<!--引入activiti的springboot启动器 -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.1.0.M6</version>
</dependency>
在配置文件中添加如下配置:
spring:
activiti:
# false:默认,数据库表不变,但是如果版本不对或者缺失表会抛出异常(生产使用)
# true:表不存在,自动创建(开发使用)
# create_drop: 启动时创建,关闭时删除表(测试使用)
# drop_create: 启动时删除表,再创建表 (不需要手动关闭引擎)
database-schema-update: true
#监测历史表是否存在,activities7默认不开启历史表
db-history-used: true
#none:不保存任何历史数据,流程中这是最高效的
#activity:只保存流程实例和流程行为
#audit:除了activity,还保存全部的流程任务以及其属性,audit为history默认值
#full:除了audit、还保存其他全部流程相关的细节数据,包括一些流程参数
history-level: full
#校验流程文件,默认校验resources下的process 文件夹的流程文件
check-process-definitions: true
启动项目,即可生成数据库表:
表分类 | 表名 | 解释 |
---|---|---|
一般数据 | ||
[ACT_GE_BYTEARRAY] | 通用的流程定义和流程资源 | |
[ACT_GE_PROPERTY] | 系统相关属性 | |
流程历史记录 | ||
[ACT_HI_ACTINST] | 历史的流程实例 | |
[ACT_HI_ATTACHMENT] | 历史的流程附件 | |
[ACT_HI_COMMENT] | 历史的说明性信息 | |
[ACT_HI_DETAIL] | 历史的流程运行中的细节信息 | |
[ACT_HI_IDENTITYLINK] | 历史的流程运行过程中用户关系 | |
[ACT_HI_PROCINST] | 历史的流程实例 | |
[ACT_HI_TASKINST] | 历史的任务实例 | |
[ACT_HI_VARINST] | 历史的流程运行中的变量信息 | |
流程定义表 | ||
[ACT_RE_DEPLOYMENT] | 部署单元信息 | |
[ACT_RE_MODEL] | 模型信息 | |
[ACT_RE_PROCDEF] | 已部署的流程定义 | |
运行实例表 | ||
[ACT_RU_EVENT_SUBSCR] | 运行时事件 | |
[ACT_RU_EXECUTION] | 运行时流程执行实例 | |
[ACT_RU_IDENTITYLINK] | 运行时用户关系信息,存储任务节点与参与者的相关信息 | |
[ACT_RU_JOB] | 运行时作业 | |
[ACT_RU_TASK] | 运行时任务 | |
[ACT_RU_VARIABLE] | 运行时变量表 |
3.2 流程设计
使用activiti modeler进行流程设计:
- 首先下载activiti-explorer:下载地址
- 下载完成后,将压缩包解压,然后将其中的activiti-explorer.war包放到tomcat的部署目录。
- 接着启动tomcat,在访问8080端口下的activiti-explorer目录就可以开始流程设计了(默认用户名和密码都为kermit)。
绘制流程图:
- 首先点击流程,然后点击流程设计工作区,再点击新建模型,建立一个流程模型。流程的名称可以任取。
- 接着绘制流程图:
- 设置节点属性:
单击节点,点击Assignments分配任务给执行人。
- 设置流程定义key:
- 保存流程,点击右上角保存按钮:
- 保存流程后,将流程导出为xml文件:
- 保存流程图,右键选择图片另存为,将流程图保存下来。
- 将xml文件和图片文件都放在resources目录下的process目录中。
3.3 Activiti常用Service接口和实现类
- RepositoryService:Activiti的资源管理类,该服务负责部署流程定义,管理流程资源。
- RuntimeService:Activiti的流程运行管理类,用于开始一个流程实例,获取关于流程执行的相关信息。
- TaskService:Activiti的任务管理类,用于处理业务运行中的各种任务,lieu查询用户任务、创建新的任务、分配任务、确定和完成一个任务等。
- HistoryService:Activiti的历史管理类,可以查询历史信息。比如,流程实例启动时间、任务的参与者、任务完成时间等。
- ManagementService:Activiti的引擎管理类,提供了对Activiti流程引擎的管理和维护功能。这些功能不在工作流驱动的应用程序中使用,主要用于Activiti系统的日常维护。
3.4 流程定义部署
将上述定义的流程部署到Activiti数据库中,可以将文件一个一个部署,也可以将文件打包成压缩包进行部署。
3.4.1 单个文件部署和压缩包部署
//用于流程定义部署的接口
@Autowired
private RepositoryService repositoryService;
@Test
public void deployProcess(){
Deployment qingjia = repositoryService.createDeployment()
//类路径指生成的target文件中,classes目录下,在源程序中,main目录下的文件可以看成是在类路径下的
.addClasspathResource("process/qingjia1.bpmn20.xml")
.addClasspathResource("process/qingjia1.png")
.name("请假流程")
.deploy();
System.out.println(qingjia.getName());
System.out.println(qingjia.getId());
}
//用于流程定义部署的接口
@Autowired
private RepositoryService repositoryService;
@Test
public void deployProcess(){
// Deployment qingjia = repositoryService.createDeployment()
// //类路径指生成的target文件中,classes目录下,在源程序中,main目录下的文件可以看成是在类路径下的
// .addClasspathResource("process/qingjia1.bpmn20.xml")
// .addClasspathResource("process/qingjia1.png")
// .name("请假流程")
// .deploy();
// System.out.println(qingjia.getName());
// System.out.println(qingjia.getId());
//部署压缩包
//定义zip输入流
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("process/qingjia.zip");
ZipInputStream zipInputStream = new ZipInputStream(resourceAsStream);
//部署流程
Deployment qingjia = repositoryService.createDeployment()
.addZipInputStream(zipInputStream)
.name("请假流程")
.deploy();
System.out.println(qingjia.getName());
System.out.println(qingjia.getId());
}
部署完成后,会向三张表中添加记录:
- act_ge_bytearray:流程资源表,每部署一个文件增加一条记录。
- act_re_procdef:流程定义表,每部署一次流程增加一条记录。
- act_re_deployment:流程定义部署表
3.4.2 启动流程实例
流程和流程实例就类似于Java类和类实例。
@Autowired
private RuntimeService runtimeService;
@Test
public void startProcess(){
//根据键启动流程实例,方法的传参即是在定义流程时指定的identifier
ProcessInstance qingjia = runtimeService.startProcessInstanceByKey("qingjia");
System.out.println(qingjia.getId());
System.out.println(qingjia.getActivityId());
System.out.println(qingjia.getProcessDefinitionId());
}
启动流程实例后,会向下表中添加记录:
act_hi_actinst:流程实例执行历史
act_hi_identitylink:流程参与用户历史信息
act_hi_procinst:流程实例历史信息
act_ru_execution:流程执行信息
act_ru_identitylink:流程参与用户信息
act_ru_task:任务信息
3.4.3 用户查询任务
@Autowired
private TaskService taskService;
@Test
public void queryTask(){
List<Task> zhangsan = taskService.createTaskQuery().taskAssignee("zhangsan").list();
for (Task task : zhangsan) {
System.out.println(task.getProcessInstanceId());
System.out.println(task.getName());
System.out.println(task.getId());
System.out.println(task.getAssignee());
}
//当流程没有执行到下一步时,查询不到lisi的任务
List<Task> lisi = taskService.createTaskQuery().taskAssignee("lisi").list();
System.out.println(lisi);
}
3.4.4 用户办理任务
@Test
public void completeTask(){
Task zhangsan = taskService.createTaskQuery().taskAssignee("zhangsan").singleResult();
//通过id完成任务
taskService.complete(zhangsan.getId());
//完成任务后,查询张三的任务和李四的任务
List<Task> zhangsan1 = taskService.createTaskQuery().taskAssignee("zhangsan").list();
System.out.println(zhangsan1);
List<Task> lisi = taskService.createTaskQuery().taskAssignee("lisi").list();
System.out.println(lisi);
}
3.4.5 查询处理过的任务
@Autowired
private HistoryService historyService;
@Test
public void historySearch(){
//历史完成任务查询
List<HistoricTaskInstance> zhangsan = historyService.createHistoricTaskInstanceQuery().taskAssignee("zhangsan").finished().list();
for(HistoricTaskInstance h:zhangsan){
System.out.println(h.getAssignee());
System.out.println(h.getId());
System.out.println(h.getName());
}
}
3.4.6 查询流程定义
@Test
public void searchProcess(){
List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery()
//可以指定排序规则
.orderByProcessDefinitionVersion().desc()
.list();
for (ProcessDefinition processDefinition : list) {
System.out.println(processDefinition.getId());
System.out.println(processDefinition.getName());
}
}
3.4.7 删除流程定义
@Test
public void delete(){
String s = "5b485fd9-e445-11ee-a952-e2c26433ada1";
//根据流程id删除流程,如果流程有实例在运行,那么会报错,如果在后面加上true,则表示如果有实例,那么实例和流程定义一起删除
repositoryService.deleteDeployment(s,true);
}
3.5 流程实例
3.5.1 BusinessKey
BusinessKey,业务标识,通常为业务的主键,业务标识与流程标识一一对应,可以通过业务标识来查找到activiti表中的流程信息。
例如,在请假时,可以将请假单的id作为业务id存储在activiti中,与对应的流程实例关联,在查询时就可以通过请假单的id查询到流程实例的处理情况。
@Test
public void businessKey(){
String bussinessKey = "qingjia1";
//启动流程实例并指定业务id
ProcessInstance qingjia = runtimeService.startProcessInstanceByKey("qingjia", bussinessKey);
System.out.println(qingjia.getBusinessKey());
System.out.println(qingjia.getId());
}
3.5.2 流程挂起和激活
流程挂起后处于暂停状态,不会被删除。
3.5.2.1 全部流程实例挂起
@Test
public void testSuspendAll(){
//获取流程实例
ProcessDefinition qingjia = repositoryService.createProcessDefinitionQuery().processDefinitionKey("qingjia").singleResult();
//再判断流程是否挂起
boolean suspended = qingjia.isSuspended();
if(!suspended){
//如果没有挂起,则进行挂起,第一个参数为;流程id,第二个参数是是否挂起,第三个参数是时间点
repositoryService.suspendProcessDefinitionById(qingjia.getId(),true,null);
}else{
//如果挂起,则激活,第二个参数表示是否激活
repositoryService.activateProcessDefinitionById(qingjia.getId(),true,null);
}
}
3.5.2.2 单个流程实例挂起
@Test
public void testSuspendSingle(){
//获得流程实例
List<ProcessInstance> list = runtimeService.createProcessInstanceQuery().list();
ProcessInstance processInstance = list.get(0);
//判断流程实例是否挂起
boolean suspended = processInstance.isSuspended();
if(suspended){
//如果已经挂起,则激活
runtimeService.activateProcessInstanceById(processInstance.getId());
}else{
//如果激活,则挂起
runtimeService.suspendProcessInstanceById(processInstance.getId());
}
}
3.5.3 任务分配
任务分配有三种方式:固定分配、表达式分配、监听器分配。
3.5.3.1 固定分配
在绘制流程图时,如果直接在Assignment属性中指定该任务的执行者,这种方式就是固定分配,固定分配之后,该任务的处理人无法变化。
3.5.3.2 表达式分配
使用UEL表达式,UEL表达式是Java EE6规范的一部分,activiti支持两种UEL表达式:UEL-value和UEL-method。
3.5.3.2.1 UEL-value
@Test
public void deployProcess(){
Deployment qingjia = repositoryService.createDeployment()
.addClasspathResource("process/qingjia.bpmn20.xml")
.addClasspathResource("process/qingjia.png")
.name("请假流程")
.deploy();
}
@Test
public void testEULValue(){
//启动一个流程实例,并指定表达式的参数
//new一个map
HashMap<String, Object> stringObjectHashMap = new HashMap<>();
stringObjectHashMap.put("assignee1","wangwu");
stringObjectHashMap.put("assignee2","zhaoliu");
//启动流程实例
runtimeService.startProcessInstanceByKey("qingjia1",stringObjectHashMap);
//查询wangwu的任务
List<Task> wangwu = taskService.createTaskQuery().taskAssignee("wangwu").list();
System.out.println(wangwu);
}
3.5.3.2.1 UEL-method
通过源代码中spring容器中的一个bean的指定方法来获取参数。
注意userBean的首字母要小写,这里是spring中的bean,所以不能大写。
@Test
public void testUELMethod(){
//启动流程实例,会自动从UserBean的方法中获得执行人
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("qingjia4");
//获取jack的任务信息
List<Task> jack = taskService.createTaskQuery().taskAssignee("jack").list();
System.out.println(jack);
}
3.5.3.3 监听器分配
使用监听器来指定负责人,在流程设计时就不需要指定assignee。
任务监听器是发生对应的相关任务事件时执行自定义的逻辑。
相关任务事件有:
- Creat:任务创建后触发
- Assignment:任务分配后触发
- Delete:任务完成后触发
- All:所有事件都触发
定义任务监听类,该类必须实现org.activiti.engine.delegate.Tasklistener接口。
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.delegate.TaskListener;
public class MyTaskListener implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
String name = delegateTask.getName();
if("组长审批".equals(name)){
delegateTask.setAssignee("lois");
}else if("主管审批".equals(name)){
delegateTask.setAssignee("tom");
}
}
}
接着定义流程:
配置任务监听器。
点击加号添加监听器,并且在任务创建时执行。
接着启动类实例:
@Test
public void testLis(){
//启动一个实例
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("qingjia6");
//启动实例并创建任务后,会自动执行监听器方法,也就是说任务被分配给了lois
List<Task> lois = taskService.createTaskQuery().taskAssignee("lois").list();
System.out.println(lois);
}
3.5.4 流程变量
流程变量分为global变量和local变量。
3.5.4.1 global变量
流程变量的默认作用域是流程实例,当一个流程变量的作用域是流程实例时,就是global变量。global变量的变量名不能重复,如果设置相同的变量名,后面的值会覆盖前面设置的值。
之前在使用UEL-value方式设置任务负责人时,使用的map就是一个global变量。所以两个中间事件都能获取到它的值。
我们可以通过UEL表达式来获取流程变量的值。
@Test
public void testGlobalVariable() {
//可以在启动流程实例、完成任务或通过流程id等方式设置全局变量
HashMap<String, Object> stringObjectHashMap = new HashMap<>();
stringObjectHashMap.put("v1", "xzcd");
//在启动实例时设置global变量
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("qingjia", stringObjectHashMap);
//通过流程实例id设置
HashMap<String, Object> stringObjectHashMap1 = new HashMap<>();
stringObjectHashMap1.put("v2", "asdcb");
runtimeService.setVariables(processInstance.getId(), stringObjectHashMap1);
//在完成任务时设置
HashMap<String, Object> stringObjectHashMap2 = new HashMap<>();
stringObjectHashMap2.put("v3","vnvmv");
Task zhangsan = taskService.createTaskQuery().taskAssignee("zhangsan").singleResult();
taskService.complete(zhangsan.getId(),stringObjectHashMap2);
}
3.5.4.2 local变量
local的作用范围只在当前任务,当前任务结束后,local变量就会清除。
@Test
public void testLocalVariable(){
HashMap<String, Object> stringObjectHashMap = new HashMap<>();
stringObjectHashMap.put("localVariable","cncmcn");
Task zhangsan = taskService.createTaskQuery().taskAssignee("zhangsan").singleResult();
//为该任务设置局部变量,作用域为当前任务
taskService.setVariablesLocal(zhangsan.getId(),stringObjectHashMap);
}
3.5.5 组任务
在流程定义中,如果为assignee分配了单个负责人,当这个负责人无法完成任务时,就需要修改流程定义,才能更换负责人,这时可以在assignee中为该任务添加多个候选人,如果有人不能完成任务时,候选人可以拾取任务并完成任务。这就是组任务。
组任务流程:
- 流程定义,在assignee中添加候选人;
- 查询指定候选人任务;
- 当前候选人拾取组任务,然后组任务变为个人任务,当前候选人成为该任务的负责人;
- 查询个人任务;
- 完成任务;
- 如果候选人在拾取任务后无法完成,那么他可以归还任务,以便其他候选人拾取任务并完成。
@SpringBootTest
public class ActivitiTest3 {
@Autowired
private RepositoryService repositoryService;
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
//流程定义部署
@Test
public void deployProcess(){
repositoryService.createDeployment()
.addClasspathResource("process/qingjia888.bpmn20.xml")
.addClasspathResource("process/qingjia.png")
.name("请假流程")
.deploy();
System.out.println("任务部署完成");
}
//启动流程实例
@Test
public void startProcess(){
runtimeService.startProcessInstanceByKey("qingjia888");
System.out.println("流程实例启动");
}
//指定候选人查询组任务
@Test
public void searchTask(){
//查询rose的组任务
List<Task> rose = taskService.createTaskQuery().taskCandidateUser("rose").list();
System.out.println("---------rose作为候选人的任务--------");
System.out.println(rose);
//查询rose作为负责人的任务
List<Task> rose1 = taskService.createTaskQuery().taskAssignee("rose").list();
System.out.println("----------rose作为负责人的任务---------");
System.out.println(rose1);
}
//拾取任务
@Test
public void claimTask(){
//查询rose的组任务
Task rose = taskService.createTaskQuery().taskCandidateUser("rose").singleResult();
//拾取任务
taskService.claim(rose.getId(),"rose");
//查询rose的任务
List<Task> rose1 = taskService.createTaskQuery().taskAssignee("rose").list();
System.out.println("---------rose拾取任务后,作为负责人的任务----------");
System.out.println(rose1);
}
//完成任务
@Test
public void completeTask(){
Task rose = taskService.createTaskQuery().taskAssignee("rose").singleResult();
taskService.complete(rose.getId());
rose = taskService.createTaskQuery().taskAssignee("rose").singleResult();
System.out.println("-----------rose拾取任务并完成任务后,作为负责人的任务-------------");
System.out.println(rose);
}
//归还任务
@Test
public void returnTask(){
//查询zhangsan是否为任务负责人
Task zhangsan = taskService.createTaskQuery().taskAssignee("zhangsan").singleResult();
if(zhangsan!=null){
//归还任务
taskService.setAssignee(zhangsan.getId(),null);
}
zhangsan = taskService.createTaskQuery().taskAssignee("zhangsan").singleResult();
System.out.println("-------------zhangsan在归还任务后,作为负责人的任务---------");
System.out.println(zhangsan);
}
//任务交接
@Test
public void test(){
//查询候选人任务
Task lisi = taskService.createTaskQuery().taskCandidateUser("lisi").singleResult();
//拾取任务
taskService.claim(lisi.getId(),"lisi");
Task zhangsan = taskService.createTaskQuery().taskAssignee("zhangsan").singleResult();
System.out.println("------------zhangsan在被交接为负责人前的任务----------");
System.out.println(zhangsan);
Task lisi1 = taskService.createTaskQuery().taskAssignee("lisi").singleResult();
//lisi无法完成任务,将其交接给张三
taskService.setAssignee(lisi1.getId(),"zhangsan");
zhangsan = taskService.createTaskQuery().taskAssignee("zhangsan").singleResult();
System.out.println("------------zhangsan被指定为负责人后的任务---------------");
System.out.println(zhangsan);
}
}
这里要解决一个bug:由于activiti在内部继承了spring security,所以当使用taskService.createTaskQuery().taskCandidateUser("rose").singleResult()
查询任务时会报错:UsernameNotFoundException
,只要实现UserDetailsService接口并重写loadUserByUsername方法就可以解决异常了。
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
return new User(s,"", Collections.emptyList());
}
}
3.5.6 网关
3.5.6.1 排他网关
只有一条路径会被选择。
绘制流程图时,单击顺序流,然后通过Flow Condition设置网关条件。
@Test
public void test(){
//查询zhangsan任务
Task zhangsan = taskService.createTaskQuery().taskAssignee("zhangsan").singleResult();
//完成任务并设置全局变量
HashMap<String, Object> stringObjectHashMap = new HashMap<>();
stringObjectHashMap.put("day",5);
taskService.complete(zhangsan.getId(),stringObjectHashMap);
//此时会走主管审批,审批人是lisi
Task lisi = taskService.createTaskQuery().taskAssignee("lisi").singleResult();
System.out.println("lisi:"+lisi);
Task wangwu = taskService.createTaskQuery().taskAssignee("wangwu").singleResult();
System.out.println(wangwu);
}
3.5.6.2 并行网关
所有分支同时执行,只有当所有分支都完成后,才能进入后续流程。
并行网关即使设置条件了也会忽略。
@Test
public void test(){
//查询zhangsan任务
Task zhangsan = taskService.createTaskQuery().taskAssignee("zhangsan").singleResult();
//完成任务
taskService.complete(zhangsan.getId());
//查询lisi和wangwu的任务
Task lisi = taskService.createTaskQuery().taskAssignee("lisi").singleResult();
Task wangwu = taskService.createTaskQuery().taskAssignee("wangwu").singleResult();
System.out.println("lisi任务:"+lisi);
System.out.println("wangwu任务:"+wangwu);
//完成lisi任务
taskService.complete(lisi.getId());
//查询lois任务
Task lois = taskService.createTaskQuery().taskAssignee("lois").singleResult();
System.out.println("wangwu没有完成任务时的lois任务:"+lois);
//完成wangwu任务
taskService.complete(wangwu.getId());
//查询lois任务
lois = taskService.createTaskQuery().taskAssignee("lois").singleResult();
System.out.println("所有分支任务完成时的lois任务:"+lois);
}
3.5.6.3 包含网关
可以同时执行多条线路,类似并行网关和排他网关的结合。
@Test
public void test(){
//查询zhangsan任务
Task zhangsan = taskService.createTaskQuery().taskAssignee("zhangsan").singleResult();
//完成任务并设置全局变量
HashMap<String, Object> stringObjectHashMap = new HashMap<>();
stringObjectHashMap.put("day",5);
taskService.complete(zhangsan.getId(),stringObjectHashMap);
//查询lisi和wangwu和lois的任务
Task lisi = taskService.createTaskQuery().taskAssignee("lisi").singleResult();
Task wangwu = taskService.createTaskQuery().taskAssignee("wangwu").singleResult();
Task lois = taskService.createTaskQuery().taskAssignee("lois").singleResult();
System.out.println("lisi任务:"+lisi);
System.out.println("wangwu任务:"+wangwu);
System.out.println("lois任务:"+lois);
}