语言确实有其局限性,但我相信:一件值得做的事情即使做的不怎么样也是值得的!
在没有专门的工作流引擎之前,为了实现流程控制,通常的做法就是采用状态字段的值来跟踪流程的变化情况。通过状态字段的取值来决定记录是否显示。
缺点:耦合性太高
通过状态字段虽然做到了流程控制,但是当我们的流程发生变更的时候,这种方式所编写的代码也要进行调整。
官方网站:https://www.activiti.org/
业务系统和流程系统剥离
Activiti是一个工作流引擎,activiti可以将业务系统中复杂的业务流程抽取出来。
使用专门的建模语言BPMN2.O进行定义,业务流程按照预先定义的流程进行执行,实现了系统的流程由activiti进行管理,减少业务系统由于流程变更进行系统升级改造的工作量,从而提高系统的健壮性,同时也减少了系统开发维护成本。
BPM(Business Process Management)即业务流程管理,是一种规范化的构造端到端的业务流程
BPMN(Business Process Model AndNotation)-业务流程模型和符号是由BPMl(BusinessProcess
Management Initiative)开发的一套标准的业务流程建模符号,使用BPMN提供的符号可以创建业务流程。
Bpmn图形其实是通过XML表示业务流程,流程图其实就是一个XML,由流程设计器读取这XML,显示为图形化界面
Activiti是一个工作流引擎(其实就是一堆jar包API),业务系统访问activiti的接口,就可以方便的操作流程相关数据,这样就可以把工作流环境与业务系统的环境集成在一起
使用activiti流程建模工具(activity-designer)定义业务流程(.bpmn文件)。.bpmn文件就是业务流程定义文件,通过xml定义业务流程。
activiti部署业务流程定义(.bpmn文件)时:
需要使用activiti提供的api把流程定义内容存储在数据库中,在activiti执行过程中可以查询定义的内容
流程实例也叫:ProcessInstance
启动一个流程实例表示开始一次业务流程的运行
注意:多个流程实例之间互相不影响
系统的业务流程交给activiti管理,通过activiti就可以查询当前流程执行到哪了,当前用户需要办理什么任务了,这些activiti帮我们管理了,而不需要开发人员自己编写sq语句查询。
用户办理任务:用户查询待办任务后,就可以办理某个任务
当任务办理完成没有下一个任务结点了,这个流程实例就完成了。
actiBPM - IntelliJ IDEs Plugin | Marketplace
点击下载
打开idea的插件
在resource/processes创建bpmn文件
定义流程,按照BPMN的规范,使用流程定义工具,用流程符号把整个流程描述出来
pom.xml文件全部内容如下
- "1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0modelVersion>
- <groupId>com.xymgroupId>
- <artifactId>activiti-projectartifactId>
- <version>1.0-SNAPSHOTversion>
- <parent>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-parentartifactId>
- <version>2.1.0.RELEASEversion>
- parent>
- <dependencies>
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-webartifactId>
- dependency>
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-jdbcartifactId>
- dependency>
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-testartifactId>
- dependency>
- <dependency>
- <groupId>org.activitigroupId>
- <artifactId>activiti-spring-boot-starterartifactId>
- <version>7.0.0.Beta2version>
- dependency>
- <dependency>
- <groupId>mysqlgroupId>
- <artifactId>mysql-connector-javaartifactId>
- dependency>
- <dependency>
- <groupId>org.projectlombokgroupId>
- <artifactId>lombokartifactId>
- dependency>
- dependencies>
- <build>
- <plugins>
- <plugin>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-maven-pluginartifactId>
- plugin>
- plugins>
- build>
- project>
- spring:
- datasource:
- driver-class-name: com.mysql.jdbc.Driver
- url: jdbc:mysql:///actspringboot?useUnicode=true&characterEconding=utf8&serverTimezone=GMT
- username: root
- password: 123456
- activiti:
- #true:表不存在会自动创建
- database-schema-update: true
- #开启历史表
- db-history-used: true
- #保存历史数据的最高级别(会保存流程相关的细节数据)
- history-level: full
- /**
- * @Description:快速实现SpringSecurity安全框架的配置
- * @Author: xiaoyumao
- * @Date: 2022/11/25 16:21
- */
- public class SecurityUtil {
- private Logger logger = LoggerFactory.getLogger(SecurityUtil.class);
-
- @Autowired
- @Qualifier("myUserDetailsService")
- private UserDetailsService userDetailsService;
-
- public void logInAs(String username) {
- UserDetails user = userDetailsService.loadUserByUsername(username);
- if (user == null) {
- throw new RuntimeException("用户" + username + "不存在");
- }
- logger.info(">Logged in as: " + username);
- SecurityContextHolder.setContext(new SecurityContextImpl(new Authentication() {
- @Override
- public Collection extends GrantedAuthority> getAuthorities() {
- return user.getAuthorities();
- }
-
- @Override
- public Object getCredentials() {
- return user.getPassword();
- }
-
- @Override
- public Object getDetails() {
- return user;
- }
-
- @Override
- public Object getPrincipal() {
- return user;
- }
-
- @Override
- public boolean isAuthenticated() {
- return true;
- }
-
- @Override
- public void setAuthenticated(boolean b) throws IllegalArgumentException {
-
- }
-
- @Override
- public String getName() {
- return user.getUsername();
- }
- }));
- org.activiti.engine.impl.identity.Authentication.setAuthenticatedUserId(username);
- }
- }
- /**
- * @Description: 实现SpringSecurity框架的用户权限的配置
- * @Author: xiaoyumao
- * @Date: 2022/11/25 16:39
- */
- @Slf4j
- @Configuration
- public class DemoApplicationConfig {
- private Logger logger = LoggerFactory.getLogger(DemoApplicationConfig.class);
-
- @Bean
- public UserDetailsService myUserDetailsService() {
- //把用户存储在内存中
- InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
- //这里添加用户,后面处理流程用到的任务负责人在这里添加
- String[][] usersGroupsAndRoles = {
- {"jack", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
- {"rose", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
- {"tom", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
- {"other", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
- {"system", "password", "ROLE_ACTIVITI_USER"},
- {"admin", "password", "ROLE_ACTIVITI_USER"},
- };
- for (String[] user : usersGroupsAndRoles) {
- //把用户的角色和组拿出来
- List
authoritiesStrings = Arrays.asList(Arrays.copyOfRange(user, 2, user.length)); - logger.info(">Registering new user: " + user[0] + "with the following Authorities[" + authoritiesStrings + "]");
- inMemoryUserDetailsManager.createUser(new User(
- user[0],
- passwordEncoder().encode(user[1]),
- authoritiesStrings.stream().map(str -> new SimpleGrantedAuthority(str)).collect(Collectors.toList())));
- }
- return inMemoryUserDetailsManager;
- }
-
- @Bean
- public PasswordEncoder passwordEncoder() {
- return new BCryptPasswordEncoder();
- }
- }
activiti7可以自动部署流程,前提是在resources目录下创建一个新的目录processes,用来放置bpmn文件
部署流程,把画好的流程定义文件,加载到数据库中,生成表的数据
- @Slf4j
- @RunWith(SpringRunner.class)
- @SpringBootTest
- public class Demo {
-
- @Autowired
- private ProcessRuntime processRuntime;
-
- @Autowired
- private TaskRuntime taskRuntime;
-
- @Autowired
- private SecurityUtil securityUtil;
-
- /**
- * 查看流程定义内容
- */
- @Test
- public void findProcess(){
- //说明哪个用户正在执行这个方法
- securityUtil.logInAs("jack");
- //流程定义的分页对象
- Page
definitionPage = processRuntime.processDefinitions(Pageable.of(0,10)); - log.info("流程定义总数:{}",definitionPage.getTotalItems());
- for (ProcessDefinition processDefinition : definitionPage.getContent()) {
- System.out.println("=====================================================");
- log.info("流程定义内容:{}",processDefinition);
- System.out.println("=====================================================");
- }
- }
- }
在数据库中,就可以看的创建的表
启动流程,使用java代码来操作数据库表中的内容
- /**
- * 启动流程
- */
- @Test
- public void startProcess(){
- //设置登录用户
- securityUtil.logInAs("system");
- ProcessInstance processInstance = processRuntime
- .start(ProcessPayloadBuilder.start().withProcessDefinitionKey("myProcess_1").build());
- log.info("流程实例内容:{}",processInstance);
- }
控制台打印
活动Activity 活动是工作或任务的一个通用术语。一个活动可以是一个任务,还可以是一个当前流程的子处理流程;其次,你还可以为活动指定不同的类型。
- /**
- * 执行任务
- */
- @Test
- public void doTask() {
- //设置登录用户 如果other用户属于别的组,查不到任务
- securityUtil.logInAs("rose");
- //查询任务
- Page
taskPage = taskRuntime.tasks(Pageable.of(0, 10)); - if (taskPage != null && taskPage.getTotalItems() > 0) {
- for (Task task : taskPage.getContent()) {
- //拾取任务
- taskRuntime.claim(TaskPayloadBuilder.claim().withTaskId(task.getId()).build());
- log.info("任务内容:{}", task);
- //完成任务
- taskRuntime.complete(TaskPayloadBuilder.complete().withTaskId(task.getId()).build());
- }
- }
- }
ACT_RE:“RE”代表“Repository”(仓库),这些表中保存一些‘静态’信息,如流程定义和流程资源(如图片、规则等);
ACT RU:'RU'表示runtime。这些运行时的表,包含流程实例,任务,变量,异步任务,等运行中的数据
activiti只在流程实例执行过程中保存这些数据,在流程结束时就会别除这些记录。这样运行时表可以一直很小速度很快。
ACT_HI* : “HI”代表“History”(历史),这些表中保存的都是历史数据,比如执行过的流程实例、变量、任务,等等。Activit默认提供了4种历史级别:
Ø none: 不保存任何历史记录,可以提高系统性能;
Ø activity:保存所有的流程实例、任务、活动信息;
Ø audit:也是Activiti的默认级别,保存所有的流程实例、任务、活动、表单属性;
Ø full:最完整的历史记录,除了包含audit级别的信息之外还能保存详细,例如:流程变量。
对于几种级别根据对功能的要求选择,如果需要日后跟踪详细可以开启full。
ACT_GE:“GE”代表“General”(通用),用在各种情况下;
此外还有两张表:ACT_EVT_LOG和ACT_PROCDEF_INFO没有按照规则来,两者分别属于HI和RE。
流程变量就是activiti在管理工作流时根据管理需要而设置的变量。在连线上使用UEL表达式,决定流程走向
流程运转有时需要靠流程变量,比如:在出差申请流程流转时如果出差天数大于3天则由经理审核,否则侧由人事直接审核,出差天数就可以设置为流程变量,在流程流转时使用。
流向Flow
流是连接两个流程节点的连线。常见的流向包含以下几种:
a、查询组任务
指定候选人,查询该候选人当前的待办任务。注意候选人不能立即办理任务。
b、拾取(claim)任务
该组任务的所有候选人都能拾取。
将候选人的组任务,变成个人任务。原来候选人就变成了该任务的负责人。
归还
如果拾取后不想办理该任务?
需要将已经拾取的个人任务归还到组里边,将个人任务变成了组任务。====
转办
c、查询个人任务
查询方式同个人任务部分,根据assignee查询用户负责的个人任务。
d、办理个人任务
网关用来处理决策,有几种常用网关需要了解:
为什么用网关
如果不用网关,传过来的流程变量不符合任何一个条件,会马上结束当前的任务(这样不好)
如果用的是排他网关,就不是结束任务,而是报错。(这样好)
排他网关
排他网关只会选择一个为true的分支执行。如果有两个分支条件都为true,排他网关会选择id值较小的一条分支去执行。
并行网关
并行网关允许将流程分成多条分支,也可以把多条分支汇聚到一起,并行网关的功能是基于进入和外出顺序流的:
fork分支:
并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。
join汇聚:
所有到达并行网关,在此等待的进入分支,直到所有进入顺序流的分支都到达以后,流程就会通过汇聚网关。与其他网关的主要区别是,并行网关不会解析条件。即使顺序流中定义了条件,也会被忽略
包含网关
包含网关的功能是基于进入和外出顺序流的:
分支:
所有外出顺序流的条件都会被解析,结果为true的顺序流会以并行方式继续执行,会为每个顺序流创建一个分支。
汇聚:
所有并行分支到达包含网关,会进入等待状态,直到每个包含流程token的进入顺序流的分支都到达。这是与并行网关的最大不同。换句话说,包含网关只会等待被选中执行了的进入顺序流。在汇聚之后,流程会穿过包含网
关继续执行。