一、为什么选择Activiti

工作流引擎对比

二、核心7大接口、28张表

7大接口
(一)7大接口
  1. RepositoryService:提供一系列管理流程部署和流程定义的API。
  2. RuntimeService:在流程运行时对流程实例进行管理与控制。
  3. TaskService:对流程任务进行管理,例如任务提醒、任务完成和创建任务等。
  4. IdentityService:提供对流程角色数据进行管理的API,这些角色数据包括用户组、用户及它们之间的关系。
  5. ManagementService:提供对流程引擎进行管理和维护的服务。
  6. HistoryService:对流程的历史数据进行操作,包括查询、删除这些历史数据。
  7. FormService:表单服务。
(二)28张表
28张表

1、act_ge_ 通用数据表,ge是general的缩写

2、act_hi_ 历史数据表,hi是history的缩写,对应HistoryService接口

3、act_id_ 身份数据表,id是identity的缩写,对应IdentityService接口

4、act_re_ 流程存储表,re是repository的缩写,对应RepositoryService接口,存储流程部署和流程定义等静态数据

5、act_ru_ 运行时数据表,ru是runtime的缩写,对应RuntimeService接口和TaskService接口,存储流程实例和用户任务等动态数据

三、创建BPMN业务流程模型

1.将Activiti提供的流程设计器应用activiti-app.war部署到Tomcat的webapps目录。

2.创建新的MySql数据库。修改activiti-app\WEB-INF\classes\META-INF\activiti-app目录下的activiti-app.properties配置文件,默认使用H2内存数据库,创建的模型重启后会丢失,改成使用MySql数据库。

3.浏览器访问http://localhost:8080/activiti-app,登录账户:admin:test

4.创建一个请假审批流程图

请假审批流程图
  • 给每个用户任务指派候选组(有权限执行当前任务的角色)

    指派候选组
    指派候选组
  • 排他网关设置条件分支表达式

    设置条件分支
    设置条件分支

    5.导出流程图为.bpmn20.xml文件

    导出xml文件

四、Spring Boot与Activiti 6.0整合

1.在POM文件中添加依赖

<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter-basic</artifactId>
<version>6.0.0</version>
</dependency>

2.将导出的.bpmn20.xml文件拷贝到项目文件夹/resources/processes下

3.application.properties文件添加配置项

spring.activiti.database-schema-update=true

databaseSchemaUpdate配置项可以设置流程引擎启动和关闭时数据库执行的策略。 databaseSchemaUpdate有以下四个值:

  • false:false为默认值,设置为该值后,Activiti在启动时,会对比数据库表中保存的版本,如果没有表或者版本不匹配时,将在启动时抛出异常。
  • true:设置为该值后,Activiti会对数据库中所有的表进行更新,如果表不存在,则Activiti会自动创建。
  • create-drop:Activiti启动时,会执行数据库表的创建操作,在Activiti关闭时,执行数据库表的删除操作。
  • drop-create:Activiti启动时,执行数据库表的删除操作在Activiti关闭时,会执行数据库表的创建操作。

4.启动应用,会在数据库里创建28张表,表创建好之后停止应用。application.properties文件修改配置项

#每次应用启动不检查Activiti数据表是否存在及版本号是否匹配,提升应用启动速度
spring.activiti.database-schema-update=false

5.application.properties文件增加配置项

#保存历史数据级别设置为full最高级别,便于历史数据的追溯
spring.activiti.history-level=full

对于历史数据,保存到何种粒度,Activiti提供了history-level属性对其进行配置。history-level属性有点像log4j的日志输出级别,该属性有以下四个值:

  • none:不保存任何的历史数据,因此,在流程执行过程中,这是最高效的。
  • activity:级别高于none,保存流程实例与流程行为,其他数据不保存。
  • audit:除activity级别会保存的数据外,还会保存全部的流程任务及其属性。audit为history的默认值。
  • full:保存历史数据的最高级别,除了会保存audit级别的数据外,还会保存其他全部流程相关的细节数据,包括一些流程参数等。

    6.完成以上步骤,就可以在程序中使用自动注入的方式,使用Activiti的7大接口。
@Autowired
private RuntimeService runtimeService; @Autowired

private TaskService taskService; @Autowired

private IdentityService identityService; @Autowired

private RepositoryService repositoryService; @Autowired

private ProcessEngine processEngine; @Autowired

private HistoryService historyService;

五、项目中的用户、角色与Activiti中的用户、用户组整合

每个项目都有自己的用户、角色表,Activiti也有自己的用户、用户组表。因此项目中的用户、角色与Activiti中的用户、用户组要做整合。

//项目中每创建一个新用户,对应的要创建一个Activiti用户
//两者的userId和userName一致
User admin=identityService.newUser("1");
admin.setLastName("admin");
identityService.saveUser(admin); //项目中每创建一个角色,对应的要创建一个Activiti用户组

Group adminGroup=identityService.newGroup("1");

adminGroup.setName("admin");

identityService.saveGroup(adminGroup); //用户与用户组关系绑定

identityService.createMembership("1","1");

六、请假审批流程

1.请假申请和请假审批数据库表设计

表设计原则:流程数据和业务数据相分离。Activiti相关表只负责流程的跳转、走向等。流程中产生的业务表单数据、审批意见、附件等存储在开发人员定义的业务表中。流程数据和业务数据之间通过processInstanceId(流程实例ID)和业务数据主键相互关联。

为什么不使用Activiti相关表来存储表单数据和附件?

activiti参数表

Activiti为了应用的灵活性和通用性采用了纵表的方式存储表单数据。假设一条请假申请表单数据有10个字段,那就需要10条记录存储原本横表只需要一条记录存储的数据。采用纵表的方式会有如下问题:

  • 会有大量的冗余数据并且数据量会急剧的增长
  • 查询语句复杂,查询效率低
  • 尤其不适合做后期的统计报表分析
activiti附件表

Activiti存储附件使用Blob数据格式,文件存储在数据库里,数据库的数据文件会变得超大,不利于数据库备份和迁移。

请假申请表结构

请假申请表

请假审批表结构

请假审批表

2.填写请假申请表单,启动流程实例

填写请假申请
//启动流程实例,字符串"vacation"是BPMN模型文件里process元素的id
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("vacation");
//流程实例启动后,流程会跳转到请假申请节点
Task vacationApply = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
//设置请假申请任务的执行人
taskService.setAssignee(vacationApply.getId(), req.getUserId().toString()); //设置流程参数:请假天数和表单ID

//流程引擎会根据请假天数days>3判断流程走向

//formId是用来将流程数据和表单数据关联起来

Map<String, Object> args = new HashMap<>();

args.put("days", req.getDays());

args.put("formId", formId); //完成请假申请任务

taskService.complete(vacationApply.getId(), args);

3.待审批列表

待审批列表
//查出当前登录用户所在的用户组
List<Group> groups = identityService.createGroupQuery()
.groupMember(String.valueOf(userId)).list();
List<String> groupNames = groups.stream()
.map(group -> group.getName()).collect(Collectors.toList()); //查询用户组的待审批请假流程列表

List<Task> tasks = taskService.createTaskQuery()

.processDefinitionKey("vacation")

.taskCandidateGroupIn(groupNames)

.listPage(pageNum - 1, pageSize); //根据流程实例ID查询请假申请表单数据

List<String> processInstanceIds = tasks.stream()

.map(task -> task.getProcessInstanceId())

.collect(Collectors.toList());

List<VacationApplyBasicPO> vacationApplyList =

vacationRepository.getVacationApplyList(processInstanceIds);

4.请假审批功能

请假审批
//查询当前审批节点
Task vacationAudit = taskService.createTaskQuery()
.taskId(req.getTaskId()).singleResult(); if (req.getAuditResult() == 1) {//审批通过

//设置流程参数:审批ID

Map<String, Object> args = new HashMap<>();

args.put("auditId", auditId);
<span class="hljs-comment"><span class="hljs-comment">//设置审批任务的执行人</span></span>
taskService.claim(vacationAudit.getId(), req.getUserId().toString());
<span class="hljs-comment"><span class="hljs-comment">//完成审批任务</span></span>
taskService.complete(vacationAudit.getId(), args);

} else {

//审批不通过,结束流程

runtimeService.deleteProcessInstance(vacationAudit.getProcessInstanceId(), auditId);

}

5.查看流程图功能

查看流程图
//controller层代码
@RequestMapping(value = "/image", method = RequestMethod.GET)
public void image(HttpServletResponse response,
@RequestParam String processInstanceId) {
try {
InputStream is = vacationService.getDiagram(processInstanceId);
if (is == null)
return;
    response.setContentType(<span class="hljs-string"><span class="hljs-string">"image/png"</span></span>);

    BufferedImage image = ImageIO.read(is);
OutputStream out = response.getOutputStream();
ImageIO.write(image, <span class="hljs-string"><span class="hljs-string">"png"</span></span>, out); is.close();
out.close();
} <span class="hljs-keyword"><span class="hljs-keyword">catch</span></span> (Exception ex) {
logger.error(<span class="hljs-string"><span class="hljs-string">"查看流程图失败"</span></span>, ex);
}

}

//service层代码

@Override

public InputStream getDiagram(String processInstanceId) {

//获得流程实例

ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()

.processInstanceId(processInstanceId).singleResult();

String processDefinitionId = StringUtils.EMPTY;

if (processInstance == null) {

//查询已经结束的流程实例

HistoricProcessInstance processInstanceHistory =

historyService.createHistoricProcessInstanceQuery()

.processInstanceId(processInstanceId).singleResult();

if (processInstanceHistory == null)

return null;

else

processDefinitionId = processInstanceHistory.getProcessDefinitionId();

} else {

processDefinitionId = processInstance.getProcessDefinitionId();

}

<span class="hljs-comment"><span class="hljs-comment">//使用宋体</span></span>
String fontName = <span class="hljs-string"><span class="hljs-string">"宋体"</span></span>;
<span class="hljs-comment"><span class="hljs-comment">//获取BPMN模型对象</span></span>
BpmnModel model = repositoryService.getBpmnModel(processDefinitionId);
<span class="hljs-comment"><span class="hljs-comment">//获取流程实例当前的节点,需要高亮显示</span></span>
List&lt;String&gt; currentActs = Collections.EMPTY_LIST;
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (processInstance != <span class="hljs-keyword"><span class="hljs-keyword">null</span></span>)
currentActs = runtimeService.getActiveActivityIds(processInstance.getId()); <span class="hljs-keyword"><span class="hljs-keyword">return</span></span> processEngine.getProcessEngineConfiguration()
.getProcessDiagramGenerator()
.generateDiagram(model, <span class="hljs-string"><span class="hljs-string">"png"</span></span>, currentActs, <span class="hljs-keyword"><span class="hljs-keyword">new</span></span> ArrayList&lt;String&gt;(),
fontName, fontName, fontName, <span class="hljs-keyword"><span class="hljs-keyword">null</span></span>, <span class="hljs-number"><span class="hljs-number">1.0</span></span>);

}

最后,spring boot应用打成jar包部署的时候,需要注意:

原文地址:https://www.jianshu.com/p/701056e672a4

Activiti6简明教程的更多相关文章

  1. 2013 duilib入门简明教程 -- 第一个程序 Hello World(3)

    小伙伴们有点迫不及待了么,来看一看Hello World吧: 新建一个空的win32项目,新建一个main.cpp文件,将以下代码复制进去: #include <windows.h> #i ...

  2. 2013 duilib入门简明教程 -- 部分bug (11)

     一.WindowImplBase的bug     在第8个教程[2013 duilib入门简明教程 -- 完整的自绘标题栏(8)]中,可以发现窗口最大化之后有两个问题,     1.最大化按钮的样式 ...

  3. 2013 duilib入门简明教程 -- 部分bug 2 (14)

        上一个教程中提到了ActiveX的Bug,即如果主窗口直接用变量生成,则关闭窗口时会产生崩溃            如果用new的方式生成,则不会崩溃,所以给出一个临时的快速解决方案,即主窗口 ...

  4. 2013 duilib入门简明教程 -- 自绘控件 (15)

        在[2013 duilib入门简明教程 -- 复杂控件介绍 (13)]中虽然介绍了界面设计器上的所有控件,但是还有一些控件并没有被放到界面设计器上,还有一些常用控件duilib并没有提供(比如 ...

  5. 2013 duilib入门简明教程 -- 事件处理和消息响应 (17)

        界面的显示方面就都讲完啦,下面来介绍下控件的响应.     前面的教程只讲了按钮和Tab的响应,即在Notify函数里处理.其实duilib还提供了另外一种响应的方法,即消息映射DUI_BEG ...

  6. 2013 duilib入门简明教程 -- FAQ (19)

        虽然前面的教程几乎把所有的知识点都罗列了,但是有很多问题经常在群里出现,所以这里再次整理一下.     需要注意的是,在下面的问题中,除了加上XML属性外,主窗口必须继承自WindowImpl ...

  7. Mac安装Windows 10的简明教程

    每次在Mac上安装Windows都是一件非常痛苦的事情,曾经为了装Win8把整台Mac的硬盘数据都弄丢了,最后通过龟速系统恢复模式恢复了MacOSX(50M电信光纤下载了3天才把系统下载完),相信和我 ...

  8. Docker简明教程

    Docker简明教程 [编者的话]使用Docker来写代码更高效并能有效提升自己的技能.Docker能打包你的开发环境,消除包的依赖冲突,并通过集装箱式的应用来减少开发时间和学习时间. Docker作 ...

  9. 2013 duilib入门简明教程 -- 总结 (20)

        duilib的入门系列就到尾声了,再次提醒下,Alberl用的duilib版本是SVN上第个版本,时间是2013.08.15~       这里给出Alberl最后汇总的一个工程,戳我下载,效 ...

随机推荐

  1. class.forName()和.class有什么区别?

    class.forName()会初始化类的成员(静态的),最先加载的是类的静态成员变量,然后是静态代码块. 访问常量并不会导致类的初始化,但是访问静态成员会.

  2. python学习笔记1-基础语法

    1 在3版本中print需要加上括号2 多行语句:用\连接 item_one=1 item_two=2 item_three=3 total = item_one + \ item_two + \ i ...

  3. Mac系统下源码编译安装MySQL 5.7.17

    1.下载并解压到:/Users/xiechunping/Softwares/mysql-5.7.17下载地址:http://ftp.ntu.edu.tw/pub/MySQL/Downloads/MyS ...

  4. 分析express-pjax

    先来看见express-pjax的源代码 module.exports = function() { return function(req, res, next) { if (req.header( ...

  5. 位运算【C++学习(计蒜客)】

    C++提供了位运算操作符,使程序可以直接对内存进行操作.C++的这个特色大大提高了C++程序的执行能力.例如使用位操作运算可以将一个存储单位中的各个二进制位左移或右移一位,也可以将一个存储单位中所有的 ...

  6. python __builtins__ frozenset类 (27)

    27.'frozenset', 返回一个冻结的集合,冻结后集合不能再添加或删除任何元素. class frozenset(object) | frozenset() -> empty froze ...

  7. bzoj 4503: 两个串【脑洞+FFT】

    真实脑洞题 因为通配符所以导致t串实际有指数级别个,任何字符串相关算法都没有用 考虑一个新的匹配方法:设a串(模板串)长为n,从m串的i位置开始匹配:\( \sum_{i=0}^{n-1}(a[j]- ...

  8. 用hdparm获取硬盘参数

    hdparm是Linux下一款能够获取和设置SATA/IDE设备参数的工具. 1.获取硬盘参数 $ sudo hdparm -i /dev/sda$ sudo hdparam -i /dev/sda ...

  9. python三行代码实现快速排序

    def quick_sort(array): if len(array) < 2: return array return quick_sort([lt for lt in array[1:] ...

  10. 数组Reduce的应用

    数组Reduce的应用 参考 简单应用 var arr = [1,2,3,4,5] var sum = arr.reduce(function (prev, cur, index, arr) { co ...