一、流程实例

1.1、什么是流程实例

流程实例(ProcessInstance)代表流程定义的执行实例。

例如:用户或程序按照流程定义内容发起一个流程,这就是一个流程实例。

1.2、启动流程实例 并添加Businesskey(业务标识)

启动流程实例时,指定的businesskey,就会在act_ru_execution ,流程实例的执行表中存储businesskey。

Businesskey:业务标识,通常为业务表的主键,业务标识和流程实例一一对应。业务标识来源于业务系统。存储业务
标识就是根据业务标识来关联查询业务系统的数据。

比如:出差流程启动一个流程实例,就可以将出差单的id作为业务标识存储到activiti中,将来查询activiti的流程实例
信息就可以获取出差单的id从而关联查询业务系统数据库得到出差单信息

下面代码是往activiti表中加Businesskey的值:

/*** 启动流程实例,添加businessKey*/@Testpublic void addBusinessKey(){// 1、得到ProcessEngineProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();// 2、得到RunTimeServiceRuntimeService runtimeService = processEngine.getRuntimeService();// 3、启动流程实例,同时还要指定业务标识businessKey,也就是出差申请单id,这里是1001ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myEvection","1001");// 4、输出processInstance相关属性System.out.println("业务id=="+processInstance.getBusinessKey());}

1.3、查询流程实例

通过下面的代码就可以获取activiti中所对应实例保存的业务Key。而这个业务Key一般都会保存相关联的业务操作表的主键,再通过主键ID去查询业务信息。

在activiti的act_ru_execution表,字段BUSINESS_KEY就是存放业务KEY的。

//查询流程实例@Testpublic void queryProcessInstance() {// 流程定义keyString processDefinitionKey = "evection";ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取RunTimeServiceRuntimeService 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());System.out.println("当前活动标识:" + processInstance.getActivityId());System.out.println("BusinessKey:" + processInstance.getBusinessKey());}}

1.4、挂起、激活流程实例

某些情况可能由于流程变更需要将当前运行的流程暂停而不是直接删除,流程暂停后将不会继续执行。

比如:报销申请每周四不可以申请,因为财务不上班,这天就可以挂起这个流程

全部流程实例挂起
操作流程定义为挂起状态,该流程定义下边所有的流程实例全部暂停:

流程定义为挂起状态该流程定义将不允许启动新的流程实例,同时该流程定义下所有的流程实例将全部挂起暂停执行。

 /*** 全部流程实例挂起与激活*/@Testpublic void SuspendAllProcessInstance() {// 获取processEngineProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();// 获取repositoryServiceRepositoryService repositoryService = processEngine.getRepositoryService();// 查询流程定义的对象ProcessDefinition processDefinition =repositoryService.createProcessDefinitionQuery().processDefinitionKey("myEvection").singleResult();// 得到当前流程定义的实例是否都为暂停状态boolean suspended = processDefinition.isSuspended();// 流程定义idString processDefinitionId = processDefinition.getId();// 判断是否为暂停if (suspended) {// 如果是暂停,可以执行激活操作 ,参数1 :流程定义id ,参数2:是否激活,参数3:激活时间repositoryService.activateProcessDefinitionById(processDefinitionId,true,null);System.out.println("流程定义:" + processDefinitionId + ",已激活");} else {// 如果是激活状态,可以暂停,参数1 :流程定义id ,参数2:是否暂停,参数3:暂停时间repositoryService.suspendProcessDefinitionById(processDefinitionId,true,null);System.out.println("流程定义:" + processDefinitionId + ",已挂起");}}

单个流程实例挂起
操作流程实例对象,针对单个流程执行挂起操作,某个流程实例挂起则此流程不再继续执行,完成该流程实例的当前任务将报异常。

/*** 单个流程实例挂起与激活*/@Testpublic void SuspendSingleProcessInstance(){// 获取processEngineProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();// RuntimeServiceRuntimeService runtimeService = processEngine.getRuntimeService();// 查询流程定义的对象ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId("12501").singleResult();// 得到当前流程定义的实例是否都为暂停状态 true--已暂停boolean suspended = processInstance.isSuspended();// 流程定义idString processDefinitionId = processInstance.getId();// 判断是否为暂停if(suspended){// 如果是暂停,可以执行激活操作 ,参数:流程定义idruntimeService.activateProcessInstanceById(processDefinitionId);System.out.println("流程定义:"+processDefinitionId+",已激活");}else{// 如果是激活状态,可以暂停,参数:流程定义idruntimeService.suspendProcessInstanceById( processDefinitionId);System.out.println("流程定义:"+processDefinitionId+",已挂起");}}

测试完成个人任务

 /*** 测试完成个人任务*/@Testpublic void completTask() {// 获取引擎ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();// 获取操作任务的服务 TaskServiceTaskService taskService = processEngine.getTaskService();// 完成任务,参数:流程实例id,完成zhangsan的任务Task task = taskService.createTaskQuery().processInstanceId("12501").taskAssignee("zhangsan").singleResult();System.out.println("流程实例id="+task.getProcessInstanceId());System.out.println("任务Id="+task.getId());System.out.println("任务负责人="+task.getAssignee());System.out.println("任务名称="+task.getName());taskService.complete(task.getId());}

二、个人任务

2.1、分配任务负责人

2.1.1、固定分配

在进行业务流程建模时指定固定的任务负责人, 如图:

并在 properties 视图中,填写 Assignee 项为任务负责人。

2.1.2、表达式分配

由于固定分配方式,任务只管一步一步执行任务,执行到每一个任务将按照 bpmn 的配置去分配任 务负责人。

2.1.2.1、表达式分配

Activiti 使用 UEL 表达式, UEL 是 java EE6 规范的一部分, UEL(Unified Expression Language)即 统一表达式语言,
activiti 支持两个 UEL 表达式: UEL-value 和 UEL-method。

UEL-value 定义

assignee 这个变量是 activiti 的一个流程变量

或者用实体类定义

user 也是 activiti 的一个流程变量, user.assignee 表示通过调用 user 的 getter 方法获取值。

UEL-method 方式

userBean 是 spring 容器中的一个 bean,表示调用该 bean 的 getUserId()方法。

UEL-method 与 UEL-value 结合
再比如: ${ldapService.findManagerForEmployee(emp)} ldapService 是 spring 容器的一个 bean,findManagerForEmployee 是该 bean 的一个方法,emp 是 activiti 流程变量, emp 作为参数传到ldapService.findManagerForEmployee 方法中。

2.1.2.2、表达式分配编写代码配置负责人

  1. 定义任务分配流程变量

  1. 设置流程变量
    在启动流程实例时设置流程变量,如下:
  //部署流程@Testpublic void processDeploymentUel(){//1. 创建ProcessEngine对象ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();//2. 得到RepositoryService实例RepositoryService repositoryService = processEngine.getRepositoryService();//3. 进行部署Deployment deployment = repositoryService.createDeployment().addClasspathResource("bpmn/evection-uel.bpmn")  //添加bpmn资源.name("出差申请流程-uel").deploy();//4. 输出部署的一些信息System.out.println("出差申请流程部署名字:"+deployment.getName());System.out.println("出差申请流程部署ID:"+deployment.getId());}//启动流程实例@Testpublic void startProcessDeploymentUel(){//1. 得到ProcessEngine对象ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();//2. 得到RunService对象RuntimeService runtimeService = processEngine.getRuntimeService();//3. 创建流程实例  流程定义的key需要知道Map<String,Object> assigneeMap = new HashMap<>();assigneeMap.put("assignee0","张三");assigneeMap.put("assignee1","李经理");assigneeMap.put("assignee2","王总经理");assigneeMap.put("assignee3","赵财务");ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myEvection1",assigneeMap);//4. 输出实例的相关信息System.out.println(processEngine.getName());}

执行成功后,可以在act_ru_variable表中看到刚才map中的数据

注意:
在代码中定义的assigneeMap ,设置的键值要和业务流程建模中设置的负责人一致:比如
assigneeMap.put(“assignee0”,“张三”),刘成武流程建模中Assignee设置成assignee0

2.1.3、监听器分配

使用监听器的方式来指定负责人,那么在流程设计时就不需要指定assignee。

任务监听器是发生对应的任务相关事件时执行自定义 java 逻辑 或表达式。 任务相当事件包括:

Event的选项包含:

Create:任务创建后触发
Assignment:任务分配后触发
Delete:任务完成后触发
All:所有事件发生都触发

定义任务监听类,且类必须实现 org.activiti.engine.delegate.TaskListener 接口

/*** 分配负责人的监听器*/
public class MyTaskListener implements TaskListener {//指定负责人@Overridepublic void notify(DelegateTask delegateTask) {if("创建申请".equals(delegateTask.getName()) &&"create".equals(delegateTask.getEventName())){delegateTask.setAssignee("张三");}}
}

点击userTask–>Task Listeners–>Listener,把监听器MyTaskListener 的全路径写进去com.sun.activiti7.config.MyTaskListener

部署和启动

/*** 监听器设置负责人*/
public class TestListener {//部署流程@Testpublic void processDeploymentListener(){//1. 创建ProcessEngine对象ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();//2. 得到RepositoryService实例RepositoryService repositoryService = processEngine.getRepositoryService();//3. 进行部署Deployment deployment = repositoryService.createDeployment().addClasspathResource("bpmn/demo-listen.bpmn")  //添加bpmn资源.name("测试监听器").deploy();//4. 输出部署的一些信息System.out.println("部署名字:"+deployment.getName());System.out.println("流程部署ID:"+deployment.getId());}//启动流程实例@Testpublic void startProcessDeploymentListener(){//1. 得到ProcessEngine对象ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();//2. 得到RunService对象RuntimeService runtimeService = processEngine.getRuntimeService();//3. 创建流程实例  流程定义的key需要知道ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("testListener");//4. 输出实例的相关信息System.out.println("流程部署ID:" + processInstance.getDeploymentId());        //流程部署IDnullSystem.out.println("流程定义ID:" + processInstance.getProcessDefinitionId()); //流程定义IDholiday:1:4System.out.println("流程实例ID:" + processInstance.getId());                  //流程实例ID2501System.out.println("活动ID:" + processInstance.getActivityId());              //活动IDnull}
}

三、 流程变量

3.1、什么是流程变量

流程变量在 activiti 中是一个非常重要的角色,流程运转有时需要靠流程变量,业务系统和 activiti 结合时少不了流程
变量,流程变量就是 activiti 在管理工作流时根据管理需要而设置的变量。 比如:在出差申请流程流转时如果出差天
数大于 3 天则由总经理审核,否则由人事直接审核, 出差天 数就可以设置为流程变量,在流程流转时使用。

注意:虽然流程变量中可以存储业务数据可以通过activiti的api查询流程变量从而实现 查询业务数据,但是不建议这
样使用,因为业务数据查询由业务系统负责,activiti设置流程变量是为了流程执行需要而创建。

3.2、流程变量类型

如果将 pojo 存储到流程变量中,必须实现序列化接口 serializable,为了防止由于新增字段无 法反序列化,需要生成
serialVersionUID。

3.3、流程变量作用域

流程变量的作用域可以是一个流程实例(processInstance),或一个任务(task),或一个执行实例 (execution)

3.3.1、globa变量

流程变量的默认作用域是流程实例。当一个流程变量的作用域为流程实例时,可以称为 global 变量
注意: 如: Global变量:userId(变量名)、zhangsan(变量值)
global 变量中变量名不允许重复,设置相同名称的变量,后设置的值会覆盖前设置的变量值。

3.3.2、local变量

任务和执行实例仅仅是针对一个任务和一个执行实例范围,范围没有流程实例大, 称为 local 变量。

注意:Local 变量由于在不同的任务或不同的执行实例中,作用域互不影响,变量名可以相同没有影响。Local 变量名也可以和 global 变量名相同,没有影响。

3.4、流程变量的使用方法

3.4.1、在属性上使用UEL表达式

可以在 assignee 处设置 UEL 表达式,表达式的值为任务的负责人,比如: ${assignee}, assignee 就是一个流程变量名称。Activiti获取UEL表达式的值,即流程变量assignee的值 ,将assignee的值作为任务的负责人进行任务分配

3.4.2、在连线上使用UEL表达式

可以在连线上设置UEL表达式,决定流程走向。
比如:${price<10000} 。price就是一个流程变量名称,uel表达式结果类型为布尔类型。
如果UEL表达式是true,要决定 流程执行走向。

3.5、使用Global变量控制流程

3.5.1、需求

员工创建出差申请单,由部门经理审核,部门经理审核通过后出差3天及以下由人财务直接审批,3天以上先由总经理
审核,总经理审核通过再由财务审批。

3.5.2、流程定义

1)、出差天数大于等于3连线条件

2)、出差天数小于3连线条件

3.5.3、需求

在部门经理审核前设置流程变量,变量值为出差单信息(包括出差天数),部门经理审核后可以根据流程变量的值决
定流程走向。
在设置流程变量时,可以在启动流程时设置,也可以在任务办理时设置

3.5.3.1、创建POJO对象

package com.sun.activiti7.pojo;import java.io.Serializable;
import java.util.Date;/*** 出差申请中的流程变量对象*/
public class Evection implements Serializable {/*** 主键Id*/private Long id;/*** 出差单的名字*/private String evectionName;/*** 出差天数*/private Double num;/*** 开始时间*/private Date beginDate;/*** 出差结束时间*/private Date endDate;/*** 目的地*/private String destination;/*** 出差原因*/private String reson;public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getEvectionName() {return evectionName;}public void setEvectionName(String evectionName) {this.evectionName = evectionName;}public Double getNum() {return num;}public void setNum(Double num) {this.num = num;}public Date getBeginDate() {return beginDate;}public void setBeginDate(Date beginDate) {this.beginDate = beginDate;}public Date getEndDate() {return endDate;}public void setEndDate(Date endDate) {this.endDate = endDate;}public String getDestination() {return destination;}public void setDestination(String destination) {this.destination = destination;}public String getReson() {return reson;}public void setReson(String reson) {this.reson = reson;}
}

3.5.3.2、启动流程时设置变量

在启动流程时设置流程变量,变量的作用域是整个流程实例。
通过Map<key,value>设置流程变量,map中可以设置多个变量,这个key就是流程变量的名字。

//部署流程@Testpublic void processDeploymentListener(){//1. 创建ProcessEngine对象ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();//2. 得到RepositoryService实例RepositoryService repositoryService = processEngine.getRepositoryService();//3. 进行部署Deployment deployment = repositoryService.createDeployment().addClasspathResource("bpmn/evection-global.bpmn")  //添加bpmn资源.name("出差申请流程--varables").deploy();//4. 输出部署的一些信息System.out.println("部署名字:"+deployment.getName());System.out.println("流程部署ID:"+deployment.getId());}//启动流程实例@Testpublic void startProcessDeploymentListener(){//1. 得到ProcessEngine对象ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();//2. 得到RunService对象RuntimeService runtimeService = processEngine.getRuntimeService();//3. 创建流程实例  流程定义的key需要知道//流程定义的KeyString key = "myEvection2";//流程变量的mapMap<String,Object> variablesMap = new HashMap<>();//设置流程变量Evection evection = new Evection();//设置出差日期evection.setNum(2d);//把流程变量的pojo放入mapvariablesMap.put("evection",evection);//设定任务的负责人variablesMap.put("assignee0","李四");variablesMap.put("assignee1","王经理");variablesMap.put("assignee2","杨总经理");variablesMap.put("assignee3","张财务");//启动流程ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(key,variablesMap);//4. 输出实例的相关信息System.out.println("流程部署ID:" + processInstance.getDeploymentId());        //流程部署IDnullSystem.out.println("流程定义ID:" + processInstance.getProcessDefinitionId()); //流程定义IDholiday:1:4System.out.println("流程实例ID:" + processInstance.getId());                  //流程实例ID2501System.out.println("活动ID:" + processInstance.getActivityId());              //活动IDnull}/*** 测试完成个人任务*/@Testpublic void completTask() {// 获取引擎ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();// 获取操作任务的服务 TaskServiceTaskService taskService = processEngine.getTaskService();// 完成任务,参数:流程实例id,完成zhangsan的任务//流程定义的KeyString key = "myEvection2";//任务负责人
//        String assingee = "李四";String assingee = "王经理";Task task = taskService.createTaskQuery().processDefinitionKey(key).taskAssignee(assingee).singleResult();System.out.println("流程实例id="+task.getProcessInstanceId());System.out.println("任务Id="+task.getId());System.out.println("任务负责人="+task.getAssignee());System.out.println("任务名称="+task.getName());if(null != task){//根据任务id完成任务taskService.complete(task.getId());}}

说明:
startProcessInstanceByKey(processDefinitionKey, variables)
流程变量作用域是一个流程实例,流程变量使用Map存储,同一个流程实例设置变量map中key相同,后者覆盖前者

3.5.3.3、任务办理时设置变量

在完成任务时设置流程变量,该流程变量只有在该任务完成后其它结点才可使用该变量,它的作用域是整个流程实
例,如果设置的流程变量的key在流程实例中已存在相同的名字则后设置的变量替换前边设置的变量。

这里需要在创建出差单任务完成时设置流程变量

//部署流程@Testpublic void processDeploymentListener(){//1. 创建ProcessEngine对象ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();//2. 得到RepositoryService实例RepositoryService repositoryService = processEngine.getRepositoryService();//3. 进行部署Deployment deployment = repositoryService.createDeployment().addClasspathResource("bpmn/evection-global.bpmn")  //添加bpmn资源.name("出差申请流程--varablesTaskComplete").deploy();//4. 输出部署的一些信息System.out.println("部署名字:"+deployment.getName());System.out.println("流程部署ID:"+deployment.getId());}//启动流程实例@Testpublic void startProcessDeploymentListener(){//1. 得到ProcessEngine对象ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();//2. 得到RunService对象RuntimeService runtimeService = processEngine.getRuntimeService();//3. 创建流程实例  流程定义的key需要知道//流程定义的KeyString key = "myEvection2";//流程变量的mapMap<String,Object> variablesMap = new HashMap<>();/*//设置流程变量Evection evection = new Evection();//设置出差日期evection.setNum(3d);//把流程变量的pojo放入mapvariablesMap.put("evection",evection);*///设定任务的负责人variablesMap.put("assignee0","李四6");variablesMap.put("assignee1","王经理6");variablesMap.put("assignee2","杨总经理6");variablesMap.put("assignee3","张财务6");//启动流程ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(key,variablesMap);//4. 输出实例的相关信息System.out.println("流程部署ID:" + processInstance.getDeploymentId());        //流程部署IDnullSystem.out.println("流程定义ID:" + processInstance.getProcessDefinitionId()); //流程定义IDholiday:1:4System.out.println("流程实例ID:" + processInstance.getId());                  //流程实例ID2501System.out.println("活动ID:" + processInstance.getActivityId());              //活动IDnull}/*** 测试完成个人任务*/@Testpublic void completTask() {// 获取引擎ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();// 获取操作任务的服务 TaskServiceTaskService taskService = processEngine.getTaskService();// 完成任务,参数:流程实例id,完成zhangsan的任务//流程定义的KeyString key = "myEvection2";//任务负责人
//        String assingee = "李四6";String assingee = "王经理6";
//          String assingee = "杨总经理1";//流程变量的mapMap<String,Object> variablesMap = new HashMap<>();//设置流程变量Evection evection = new Evection();//设置出差日期evection.setNum(2d);//把流程变量的pojo放入mapvariablesMap.put("evection",evection);Task task = taskService.createTaskQuery().processDefinitionKey(key).taskAssignee(assingee).singleResult();System.out.println("流程实例id="+task.getProcessInstanceId());System.out.println("任务Id="+task.getId());System.out.println("任务负责人="+task.getAssignee());System.out.println("任务名称="+task.getName());if(null != task){//根据任务id完成任务taskService.complete(task.getId(),variablesMap);System.out.println("任务已完成"+task.getId());}}

注意:在分支的最后一个节点设置变量
比如:在此案例中,王经理6时设置变量,李四6不用设置变量,因为在王经理6审批后面有个判断分支

3.5.4、操作数据库表

设置流程变量会在当前执行流程变量表插入记录,同时也会在历史流程变量表也插入记录。

//当前流程变量表
SELECT * FROM act_ru_variable
#历史流程变量表
SELECT * FROM act_hi_varinst

记录当前运行流程实例可使用的流程变量,包括 global和local变量

3.6、设置local流程变量

3.6.1、任务办理时设置

任务办理时设置local流程变量,当前运行的流程实例只能在该任务结束前使用,任务结束该变量无法在当前流程实例
使用,可以通过查询历史任务查询。

 //部署流程@Testpublic void processDeploymentListener(){//1. 创建ProcessEngine对象ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();//2. 得到RepositoryService实例RepositoryService repositoryService = processEngine.getRepositoryService();//3. 进行部署Deployment deployment = repositoryService.createDeployment().addClasspathResource("bpmn/evection-global.bpmn")  //添加bpmn资源.name("出差申请流程--TestVarablesLocal").deploy();//4. 输出部署的一些信息System.out.println("部署名字:"+deployment.getName());System.out.println("流程部署ID:"+deployment.getId());}//启动流程实例@Testpublic void startProcessDeploymentListener(){//1. 得到ProcessEngine对象ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();//2. 得到RunService对象RuntimeService runtimeService = processEngine.getRuntimeService();//3. 创建流程实例  流程定义的key需要知道//流程定义的KeyString key = "myEvection2";//流程变量的mapMap<String,Object> variablesMap = new HashMap<>();/*//设置流程变量Evection evection = new Evection();//设置出差日期evection.setNum(3d);//把流程变量的pojo放入mapvariablesMap.put("evection",evection);*///设定任务的负责人variablesMap.put("assignee0","李四8");variablesMap.put("assignee1","王经理8");variablesMap.put("assignee2","杨总经理8");variablesMap.put("assignee3","张财务8");//启动流程ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(key,variablesMap);//4. 输出实例的相关信息System.out.println("流程部署ID:" + processInstance.getDeploymentId());        //流程部署IDnullSystem.out.println("流程定义ID:" + processInstance.getProcessDefinitionId()); //流程定义IDholiday:1:4System.out.println("流程实例ID:" + processInstance.getId());                  //流程实例ID2501System.out.println("活动ID:" + processInstance.getActivityId());              //活动IDnull}/*** 测试完成个人任务*/@Testpublic void completTask() {// 获取引擎ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();// 获取操作任务的服务 TaskServiceTaskService taskService = processEngine.getTaskService();// 完成任务,参数:流程实例id,完成zhangsan的任务//流程定义的KeyString key = "myEvection2";//任务负责人
//        String assingee = "李四8";String assingee = "王经理8";
//          String assingee = "杨总经理8";//流程变量的mapMap<String,Object> variablesMap = new HashMap<>();//设置流程变量Evection evection = new Evection();//设置出差日期evection.setNum(2d);//把流程变量的pojo放入mapvariablesMap.put("evection",evection);Task task = taskService.createTaskQuery().processDefinitionKey(key).taskAssignee(assingee).singleResult();System.out.println("流程实例id="+task.getProcessInstanceId());System.out.println("任务Id="+task.getId());System.out.println("任务负责人="+task.getAssignee());System.out.println("任务名称="+task.getName());if(null != task){// 设置local变量,作用域为该任务taskService.setVariablesLocal(task.getId(),variablesMap);//根据任务id完成任务taskService.complete(task.getId());System.out.println("任务已完成"+task.getId());}}

3.6.2、通过当前任务设置

 /*** 、通过当前任务设置参数*/@Testpublic void setLocalVariableByTaskId(){//流程定义的KeyString key = "myEvection2";//任务负责人String assingee = "王经理8";// 获取processEngineProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();TaskService taskService = processEngine.getTaskService();Evection evection = new Evection ();evection.setNum(3d);// 当前待办任务idTask task = taskService.createTaskQuery().processDefinitionKey(key).taskAssignee(assingee).singleResult();// 通过任务设置流程变量taskService.setVariableLocal(task.getId(), "evection", evection);// 一次设置多个值//taskService.setVariablesLocal(taskId, variables)}

3.6.3、获取流程变量

/**获取流程变量*/@Testpublic void getProcessVariables() {//流程定义的KeyString key = "myEvection2";//任务负责人String assingee = "王经理8";// 获取processEngineProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();TaskService taskService = processEngine.getTaskService();// 当前待办任务idTask task = taskService.createTaskQuery().processDefinitionKey(key).taskAssignee(assingee).singleResult();// 获取流程变量【javaBean类型】Evection evection = (Evection) taskService.getVariableLocal(task.getId(), "evection");System.out.println(evection.getId()+"******"+evection.getNum());}

3.6.4、查询历史流程变量

 /**查询历史的流程变量*/@Testpublic void getHistoryProcessVariables(){// 获取processEngineProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();List<HistoricVariableInstance> list = processEngine.getHistoryService().createHistoricVariableInstanceQuery()//创建一个历史的流程变量查询.variableName("evection").list();if(list != null && list.size()>0){for(HistoricVariableInstance hiv : list){System.out.println(hiv.getTaskId()+"  "+hiv.getVariableName()+"       "+hiv.getValue()+"      "+hiv.getVariableTypeName());}}}

四、组任务

4.1、需求

在流程定义中在任务结点的 assignee 固定设置任务负责人,在流程定义时将参与者固定设置在.bpmn 文件中,如果临
时任务负责人变更则需要修改流程定义,系统可扩展性差。
针对这种情况可以给任务设置多个候选人,可以从候选人中选择参与者来完成任务。

4.2、设置任务候选人

在流程图中任务节点的配置中设置 candidate-users(候选人),多个候选人之间用逗号分开。


查看bpmn文件

<userTask activiti:candidateUsers="lisi,wangwu" activiti:exclusive="true" id="_3" name="经理审
批"/>

我们可以看到部门经理的审核人已经设置为 lisi,wangwu 这样的一组候选人,可以使用

4.3、组任务

组任务办理流程

  1. 查询组任务
    指定候选人,查询该候选人当前的待办任务。
    候选人不能立即办理任务。

  2. 拾取(claim)任务
    该组任务的所有候选人都能拾取。
    将候选人的组任务,变成个人任务。原来候选人就变成了该任务的负责人。

  3. 查询个人任务
    查询方式同个人任务部分,根据assignee查询用户负责的个人任务。

  4. 办理个人任务

4.4、查询组任务

 //启动流程实例@Testpublic void startProcessDeploymentListener(){//1. 得到ProcessEngine对象ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();//2. 得到RunService对象RuntimeService runtimeService = processEngine.getRuntimeService();//3. 创建流程实例  流程定义的key需要知道//流程定义的KeyString key = "testCandidate";//启动流程ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(key);//4. 输出实例的相关信息System.out.println("流程部署ID:" + processInstance.getDeploymentId());        //流程部署IDnullSystem.out.println("流程定义ID:" + processInstance.getProcessDefinitionId()); //流程定义IDholiday:1:4System.out.println("流程实例ID:" + processInstance.getId());                  //流程实例ID2501System.out.println("活动ID:" + processInstance.getActivityId());              //活动IDnull}/*** 完成个人任务*/@Testpublic void completTask() {// 获取引擎ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();// 获取操作任务的服务 TaskServiceTaskService taskService = processEngine.getTaskService();// 完成任务,参数:流程实例id,完成zhangsan的任务//流程定义的KeyString key = "testCandidate";//任务负责人String assingee = "汤姆";//查询任务Task task = taskService.createTaskQuery().processDefinitionKey(key).taskAssignee(assingee).singleResult();System.out.println("流程实例id="+task.getProcessInstanceId());System.out.println("任务Id="+task.getId());System.out.println("任务负责人="+task.getAssignee());System.out.println("任务名称="+task.getName());if(null != task){//根据任务id完成任务taskService.complete(task.getId());System.out.println("任务已完成"+task.getId());}}/*** 查询组任务*/@Testpublic void findGroupTaskList() {// 流程定义keyString processDefinitionKey = "testCandidate";// 任务候选人String candidateUser = "wangwu";// 获取processEngineProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();// 创建TaskServiceTaskService taskService = processEngine.getTaskService();//查询组任务List<Task> list = taskService.createTaskQuery().processDefinitionKey(processDefinitionKey).taskCandidateUser(candidateUser)//根据候选人查询.list();for (Task task : list) {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());}}

4.5、拾取组任务

@Testpublic void claimTask(){// 获取processEngineProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();// 创建TaskServiceTaskService taskService = processEngine.getTaskService();//要拾取的任务idString taskId = "95002";//任务候选人idString userId = "wangwu";//拾取任务//即使该用户不是候选人也能拾取(建议拾取时校验是否有资格)//校验该用户有没有拾取任务的资格Task task = taskService.createTaskQuery().taskId(taskId).taskCandidateUser(userId)//根据候选人查询.singleResult();if(task!=null){//拾取任务taskService.claim(taskId, userId);System.out.println("****用户****"+userId+"****任务拾取***"+taskId+"成功");}}

4.6、查询个人待办任务

 /*** 查询个人待办任务*/@Testpublic void findPersonalTaskList() {// 流程定义keyString processDefinitionKey = "testCandidate";// 任务负责人String assignee = "wangwu";// 获取processEngineProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();// 创建TaskServiceTaskService taskService = processEngine.getTaskService();List<Task> list = taskService.createTaskQuery().processDefinitionKey(processDefinitionKey).taskAssignee(assignee).list();for (Task task : list) {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());}}

4.6、完成个人任务

 /*** 完成个人任务*/@Testpublic void completTask2() {// 获取引擎ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();// 获取操作任务的服务 TaskServiceTaskService taskService = processEngine.getTaskService();// 完成任务,参数:流程实例id,完成zhangsan的任务//流程定义的KeyString key = "testCandidate";//任务负责人String assingee = "wangwu";//查询任务Task task = taskService.createTaskQuery().processDefinitionKey(key).taskAssignee(assingee).singleResult();System.out.println("流程实例id="+task.getProcessInstanceId());System.out.println("任务Id="+task.getId());System.out.println("任务负责人="+task.getAssignee());System.out.println("任务名称="+task.getName());if(null != task){//根据任务id完成任务taskService.complete(task.getId());System.out.println("任务已完成"+task.getId());}}

4.6、归还组任务

 /**归还组任务,由个人任务变为组任务,还可以进行任务交接*/@Testpublic void setAssigneeToGroupTask() {// 获取processEngineProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();// 查询任务使用TaskServiceTaskService taskService = processEngine.getTaskService();// 当前待办任务String taskId = "95002";// 任务负责人String assignee = "wangwu";// 校验assignee是否是taskId的负责人,如果是负责人才可以归还组任务Task task = taskService.createTaskQuery().taskId(taskId).taskAssignee(assignee).singleResult();if (task != null) {// 如果设置为null,归还组任务,该 任务没有负责人taskService.setAssignee(taskId, null);System.out.println("***taskId***"+taskId+"任务归还成功");}}

4.6、任务交接

 /*** 任务交接*/@Testpublic void setAssigneeToCandidateUser() {// 获取processEngineProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();// 查询任务使用TaskServiceTaskService taskService = processEngine.getTaskService();// 当前待办任务String taskId = "95002";// 任务负责人String assignee = "wangwu";// 将此任务交给其它候选人办理该任务String candidateuser = "lisi";// 校验userId是否是taskId的负责人,如果是负责人才可以归还组任务Task task = taskService.createTaskQuery().taskId(taskId).taskAssignee(assignee).singleResult();if (task != null) {taskService.setAssignee(taskId, candidateuser);System.out.println("***taskId***"+taskId+"交接给"+candidateuser);}}

五、网关

网关用来控制流程的流向

5.1、排他网关ExclusiveGateway

5.1.1、 什么是排他网关

排他网关,用来在流程中实现决策。 当流程执行到这个网关,所有分支都会判断条件是否为true,如果为true则执行
该分支,

注意:
排他网关只会选择一个为true的分支执行。如果有两个分支条件都为true,排他网关会选择id值较小的一条分
支去执行。
如果都为false会报错

org.activiti.engine.ActivitiException: No outgoing sequence flow of the exclusive gateway
‘exclusivegateway1’ could be selected for continuing the process
at
org.activiti.engine.impl.bpmn.behavior.ExclusiveGatewayActivityBehavior.leave(ExclusiveGatewa
yActivityBehavior.java:85)

为什么要用排他网关?
不用排他网关也可以实现分支,如:在连线的condition条件上设置分支条件。
在连线设置condition条件的缺点:如果条件都不满足,流程就结束了(是异常结束)。
如果 使用排他网关决定分支的走向,如下:

5.1.2、 流程定义

排他网关图标,红框内:

5.1.3、 测试

在部门经理审核后,走排他网关,从排他网关出来的分支有两条,一条是判断出差天数是否大于3天,另一条是判断
出差天数是否小于等于3天。

 //部署流程@Testpublic void processDeploymentListener(){//1. 创建ProcessEngine对象ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();//2. 得到RepositoryService实例RepositoryService repositoryService = processEngine.getRepositoryService();//3. 进行部署Deployment deployment = repositoryService.createDeployment().addClasspathResource("bpmn/evection-exclusive.bpmn")  //添加bpmn资源.name("出差申请流程--排他网关").deploy();//4. 输出部署的一些信息System.out.println("部署名字:"+deployment.getName());System.out.println("流程部署ID:"+deployment.getId());}//启动流程实例@Testpublic void startProcessDeploymentListener(){//1. 得到ProcessEngine对象ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();//2. 得到RunService对象RuntimeService runtimeService = processEngine.getRuntimeService();//3. 创建流程实例  流程定义的key需要知道//流程定义的KeyString key = "exclusive";//流程变量的mapMap<String,Object> map = new HashMap<>();//设置流程变量Evection evection = new Evection();//设置出差日期evection.setNum(2d);//把流程变量的pojo放入mapmap.put("evection",evection);//启动流程ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(key,map);//4. 输出实例的相关信息System.out.println("流程部署ID:" + processInstance.getDeploymentId());        //流程部署IDnullSystem.out.println("流程定义ID:" + processInstance.getProcessDefinitionId()); //流程定义IDholiday:1:4System.out.println("流程实例ID:" + processInstance.getId());                  //流程实例ID2501System.out.println("活动ID:" + processInstance.getActivityId());              //活动IDnull}/*** 完成个人任务*/@Testpublic void completTask() {// 获取引擎ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();// 获取操作任务的服务 TaskServiceTaskService taskService = processEngine.getTaskService();// 完成任务,参数:流程实例id,完成zhangsan的任务//流程定义的KeyString key = "exclusive";//任务负责人//        String assingee = "tom";String assingee = "jerry";//查询任务Task task = taskService.createTaskQuery().processDefinitionKey(key).taskAssignee(assingee).singleResult();System.out.println("流程实例id="+task.getProcessInstanceId());System.out.println("任务Id="+task.getId());System.out.println("任务负责人="+task.getAssignee());System.out.println("任务名称="+task.getName());if(null != task){//根据任务id完成任务taskService.complete(task.getId());System.out.println("任务已完成"+task.getId());}}

5.2、并行网关ParallelGateway

5.2.1、什么是并行网关

并行网关允许将流程分成多条分支,也可以把多条分支汇聚到一起,并行网关的功能是基于进入和外出顺序流的:

fork分支:
并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。
join汇聚:
分支到达并行网关,在此等待的进入分支, 直到所有进入顺序流的分支都到达以后, 流程就会通过汇聚网关。
注意:
与其他网关的主要区别是,并行网关不会解析条件。 即使顺序流中定义了条件,也会被忽略,也就是说每条进入流都会执行。
汇聚网关通过的条件是所以的分支都到达了汇聚网关。

说明:
技术经理和项目经理是两个execution分支,在act_ru_execution表有两条记录分别是技术经理和项目经理,
act_ru_execution还有一条记录表示该流程实例。
待技术经理和项目经理任务全部完成,在汇聚点汇聚,通过parallelGateway并行网关。
并行网关在业务应用中常用于会签任务,会签任务即多个参与者共同办理的任务。

5.2.2、流程定义

并行网关图标,红框内:

5.2.3、 测试

 //部署流程@Testpublic void processDeploymentListener(){//1. 创建ProcessEngine对象ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();//2. 得到RepositoryService实例RepositoryService repositoryService = processEngine.getRepositoryService();//3. 进行部署Deployment deployment = repositoryService.createDeployment().addClasspathResource("bpmn/evection-parallel.bpmn")  //添加bpmn资源.name("出差申请流程--并行网关").deploy();//4. 输出部署的一些信息System.out.println("部署名字:"+deployment.getName());System.out.println("流程部署ID:"+deployment.getId());}//启动流程实例@Testpublic void startProcessDeploymentListener(){//1. 得到ProcessEngine对象ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();//2. 得到RunService对象RuntimeService runtimeService = processEngine.getRuntimeService();//3. 创建流程实例  流程定义的key需要知道//流程定义的KeyString key = "parallel";//流程变量的mapMap<String,Object> map = new HashMap<>();//设置流程变量Evection evection = new Evection();//设置出差日期evection.setNum(4d);//把流程变量的pojo放入mapmap.put("evection",evection);//启动流程ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(key,map);//4. 输出实例的相关信息System.out.println("流程部署ID:" + processInstance.getDeploymentId());        //流程部署IDnullSystem.out.println("流程定义ID:" + processInstance.getProcessDefinitionId()); //流程定义IDholiday:1:4System.out.println("流程实例ID:" + processInstance.getId());                  //流程实例ID2501System.out.println("活动ID:" + processInstance.getActivityId());              //活动IDnull}/*** 完成个人任务*/@Testpublic void completTask() {// 获取引擎ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();// 获取操作任务的服务 TaskServiceTaskService taskService = processEngine.getTaskService();// 完成任务,参数:流程实例id,完成zhangsan的任务//流程定义的KeyString key = "parallel";//任务负责人
//        String assingee = "tom";
//        String assingee = "jerry";String assingee = "jack";//查询任务Task task = taskService.createTaskQuery().processDefinitionKey(key).taskAssignee(assingee).singleResult();System.out.println("流程实例id="+task.getProcessInstanceId());System.out.println("任务Id="+task.getId());System.out.println("任务负责人="+task.getAssignee());System.out.println("任务名称="+task.getName());if(null != task){//根据任务id完成任务taskService.complete(task.getId());System.out.println("任务已完成"+task.getId());}}

5.3、包含网关InclusiveGateway

5.3.1、什么是包含网关

包含网关可以看做是排他网关和并行网关的结合体。
和排他网关一样,你可以在外出顺序流上定义条件,包含网关会解析它们。 但是主要的区别是包含网关可以选择多
于一条顺序流,这和并行网关一样。
包含网关的功能是基于进入和外出顺序流的:

分支:
所有外出顺序流的条件都会被解析,结果为true的顺序流会以并行方式继续执行, 会为每个顺序流创建一个分支。

l 汇聚:
所有并行分支到达包含网关,会进入等待状态, 直到每个包含流程token的进入顺序流的分支都到达。 这是与并行网
关的最大不同。换句话说,包含网关只会等待被选中执行了的进入顺序流。 在汇聚之后,流程会穿过包含网关继续
执行。

5.3.2、流程定义:

出差申请大于等于3天需要由项目经理审批,小于3天由技术经理审批,出差申请必须经过人事经理审批。

包含网关图标,红框内:

定义流程:

5.3.3、测试

 //部署流程@Testpublic void processDeploymentListener(){//1. 创建ProcessEngine对象ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();//2. 得到RepositoryService实例RepositoryService repositoryService = processEngine.getRepositoryService();//3. 进行部署Deployment deployment = repositoryService.createDeployment().addClasspathResource("bpmn/evection-inclusive.bpmn")  //添加bpmn资源.name("出差申请流程--包含网关").deploy();//4. 输出部署的一些信息System.out.println("部署名字:"+deployment.getName());System.out.println("流程部署ID:"+deployment.getId());}//启动流程实例@Testpublic void startProcessDeploymentListener(){//1. 得到ProcessEngine对象ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();//2. 得到RunService对象RuntimeService runtimeService = processEngine.getRuntimeService();//3. 创建流程实例  流程定义的key需要知道//流程定义的KeyString key = "inclusive";//流程变量的mapMap<String,Object> map = new HashMap<>();//设置流程变量Evection evection = new Evection();//设置出差日期evection.setNum(4d);//把流程变量的pojo放入mapmap.put("evection",evection);//启动流程ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(key,map);//4. 输出实例的相关信息System.out.println("流程部署ID:" + processInstance.getDeploymentId());        //流程部署IDnullSystem.out.println("流程定义ID:" + processInstance.getProcessDefinitionId()); //流程定义IDholiday:1:4System.out.println("流程实例ID:" + processInstance.getId());                  //流程实例ID2501System.out.println("活动ID:" + processInstance.getActivityId());              //活动IDnull}/*** 完成个人任务*/@Testpublic void completTask() {// 获取引擎ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();// 获取操作任务的服务 TaskServiceTaskService taskService = processEngine.getTaskService();// 完成任务,参数:流程实例id,完成zhangsan的任务//流程定义的KeyString key = "inclusive";//任务负责人
//        String assingee = "tom";
//        String assingee = "miki";//        String assingee = "jerry";String assingee = "jack";//查询任务Task task = taskService.createTaskQuery().processDefinitionKey(key).taskAssignee(assingee).singleResult();System.out.println("流程实例id="+task.getProcessInstanceId());System.out.println("任务Id="+task.getId());System.out.println("任务负责人="+task.getAssignee());System.out.println("任务名称="+task.getName());if(null != task){//根据任务id完成任务taskService.complete(task.getId());System.out.println("任务已完成"+task.getId());}}

小结:在分支时,需要判断条件,符合条件的分支,将会执行,符合条件的分支最终才进行汇聚。

Activiti进阶相关推荐

  1. Activiti进阶(一)——HelloWorld

    任何知识的学习都是有简入繁,我们的Activiti也不例外,这篇博文将通过一个简单的HelloWorld来带大家认识一下我们的Activiti; 一.画流程图 在画流程图之前,要确保自己的Eclips ...

  2. Activiti进阶(九)——接收任务(ReceiveTask)

    接收任务(ReceiveTask)即等待任务,接收任务是一个简单任务,它会等待对应消息的到达.当前,官方只实现 了这个任务的java语义. 当流程达到接收任务,流程状态会保存到数据库中.在任务创建后, ...

  3. Activiti进阶-个人任务

    二.个人任务 2.1.分配任务负责人 2.1.1.固定分配 在进行业务流程建模时指定固定的任务负责人, 如图: 并在 properties 视图中,填写 Assignee 项为任务负责人. 2.1.2 ...

  4. Activiti进阶(七)——排他网关(ExclusiveGateWay)

    转载地址:http://blog.csdn.net/zjx86320?viewmode=contents 上一篇博文我们简单介绍了一下连线,也分析了一种情况,就是在工作流中,用户任务下面有两个连线,如 ...

  5. 工作流引擎 Activiti 万字详细进阶

    Activiti进阶 一.流程实例 什么是流程实例 流程实例(ProcessInstance)代表流程定义的执行实例. 一个流程实例包括了所有的运行节点.我们可以利用这个对象来了解当前流程实例的进度等 ...

  6. Activiti工作流引擎进阶【收藏可做笔记系列】

    Activiti工作流引擎进阶 Activiti进阶 一.流程实例 什么是流程实例 启动流程实例 并添加Businesskey(业务标识) 操作数据库表 查询流程实例 关联BusinessKey 挂起 ...

  7. 【Activity学习五】--基于SSM整合Activiti之请假流程实现(二)

    [Activity学习五]--基于SSM整合Activiti之请假流程实现(二) 1.部署流程资源 2.查询流程定义信息 3.发布请假流程 4.查询用户任务 5.提出请假 6.老板查看请假任务 7.老 ...

  8. Activiti7学习笔记、非常详细 | 进阶篇

    Activiti进阶 一.流程实例 什么是流程实例 流程实例(ProcessInstance)代表流程定义的执行实例. 一个流程实例包括了所有的运行节点.我们可以利用这个对象来了解当前流程实例的进度等 ...

  9. 【笔记整理】Activiti工作流的学习笔记

    Activiti基础篇 1.什么是工作流 1.1 工作流简介 工作流(Workflow),就是通过计算机对业务流程自动化执行管理.它主要解决的是"使在多个参与者之间按照某种预定义的规则自动进 ...

最新文章

  1. python去除中间空格只留一个_汇总初学Python的21个操作难点,看完别再去踩坑了...
  2. Markdown基础语法小结
  3. Oracle 体系结构2 - 共享和专用服务器
  4. redis set数据类型常用命令及应用场景
  5. Redis在PHP项目中的应用
  6. Gitter - 高颜值GitHub小程序客户端诞生记
  7. spring中配置ioc中的常用注解
  8. OpenCV_cv::Mat初始化
  9. Protel99SE推荐使用英文版
  10. 微机原理是微型计算机与接口技术吗,《微机原理与接口技术》课程教学大纲
  11. 人工智能降噪PS插件 Topaz DeNoise AI
  12. win10如何打开计算机窗口,处理win10中电脑任务栏不显示打开窗口的方法
  13. 阿里发布虚拟美女“俪知”,会说东北话、四川话、河南话和粤语等
  14. 人人憎恨的大数据杀熟你了解吗? 大数据杀熟”是否真的存在?
  15. 浅入浅出 1.7和1.8的 HashMap
  16. 《Android开发源码精编解析》最新PDF版开源,安卓工程师进阶实战
  17. 浙江理工c语言复试试题,2016年浙江理工大学信息学院C语言程序设计复试笔试最后押题五套卷...
  18. 文笔极佳的郭靖夫妇悼文
  19. 基于ZKEACMS的.Net Core多租户CMS建站系统
  20. SN74LVC8T245 8路 电平转换 3.3v 5v

热门文章

  1. pb11.5破解补丁
  2. 一天刷到5篇「x is All You Need」,当学术论文开始标题党……
  3. Java根据子节点获取最上层节点(根节点)数据和所有上级集合
  4. Stairway to SQL Server Security Level 3: Principals and Securables - SQLServerCentral
  5. Shell---函数
  6. K-Mediods算法
  7. Eclipse怎么设置字体呢
  8. Python库下载安装教程
  9. C语言求解三个数的中间值
  10. excel怎么做汇总平均值?