SpringBoot + Activiti7 工作流之第 1 篇 —— 【基础篇】认识 Activiti,创建 Activiti 25 张表,流程设计器使用
SpringBoot + Activiti7 工作流之第 2 篇 —— 【基础篇】Activiti 流程定义的相关操作
SpringBoot + Activiti7 工作流之第 3 篇 —— 【进阶篇】流程实例、个人任务
SpringBoot + Activiti7 工作流之第 4 篇 —— 【进阶篇】流程变量
SpringBoot + Activiti7 工作流之第 5 篇 —— 【进阶篇】组任务
SpringBoot + Activiti7 工作流之第 6 篇 —— 【进阶篇】网关
SpringBoot + Activiti7 工作流之第 7 篇 —— 整合 SpringSecurity 项目实战(完结)
从微服务迁移到工作流的经验之谈

Activiti7 工作流之第 7 篇 —— 整合 SpringSecurity 项目实战(完结)

技术选型:SpringBoot2.0+、Activiti7、SpringSecurity、Mybatis、MySQL。

本篇博客代码地址(含全部数据库表):
代码地址:https://pan.baidu.com/s/1RNgxtVgKkv1pnIfBjyZWMA 提取码:pyuy

自动生产activiti7表

1、新建数据库

image.png

2、配置activiti.cfg.xml,执行testCreateDbTable 方法

在 test 包下找到 test01 运行 testCreateDbTable 测试方法即可自动生成 Activiti 的 25 张表,需要配置数据库的连接信息:activiti.cfg.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xmlns:tx="http://www.springframework.org/schema/tx"
  6. xsi:schemaLocation="http://www.springframework.org/schema/beans
  7. http://www.springframework.org/schema/beans/spring-beans.xsd
  8. http://www.springframework.org/schema/contex
  9. http://www.springframework.org/schema/context/spring-context.xsd
  10. http://www.springframework.org/schema/tx
  11. http://www.springframework.org/schema/tx/spring-tx.xsd">
  12. <!-- 默认id对应的值 为processEngineConfiguration -->
  13. <!-- processEngine Activiti的流程引擎 -->
  14. <bean id="processEngineConfiguration"
  15. class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
  16. <property name="jdbcDriver" value="com.mysql.jdbc.Driver"/>
  17. <property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/study_activiti?useUnicode=true&amp;characterEncoding=utf-8&amp;serverTimezone=UTC"/>
  18. <property name="jdbcUsername" value="root"/>
  19. <property name="jdbcPassword" value="root"/>
  20. <!-- activiti数据库表处理策略:自动更新 -->
  21. <property name="databaseSchemaUpdate" value="true"/>
  22. </bean>
  23. </beans>

3、初始化用户表

这里罗列出 4 张业务表:并且有了 user 的测试数据。zhangsan -> lisi -> wangwu -> zhuliu (这是审批流程,密码都是 123)

  1. DROP TABLE IF EXISTS `t_apply_leave`;
  2. CREATE TABLE `t_apply_leave` (
  3. `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  4. `apply_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '请假标题',
  5. `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户',
  6. `day` double(4, 1) NULL DEFAULT NULL COMMENT '请假天数,保留1位小数',
  7. `begin_date` date NULL DEFAULT NULL COMMENT '开始日期',
  8. `end_date` date NULL DEFAULT NULL COMMENT '结束日期',
  9. `reason` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '请假事由',
  10. `state` int(11) NULL DEFAULT NULL COMMENT '状态:0=开始录入,1=开始审批,2=审批完成',
  11. PRIMARY KEY (`id`) USING BTREE
  12. ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
  13. -- ----------------------------
  14. -- Table structure for t_flow
  15. -- ----------------------------
  16. DROP TABLE IF EXISTS `t_flow`;
  17. CREATE TABLE `t_flow` (
  18. `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  19. `flow_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '部署的名称',
  20. `flow_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '部署的key',
  21. `file_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文件路径',
  22. `state` int(11) NULL DEFAULT NULL COMMENT '部署状态:1=已部署,0=未部署',
  23. `create_time` datetime(0) NULL DEFAULT NULL COMMENT '部署时间',
  24. PRIMARY KEY (`id`) USING BTREE
  25. ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
  26. -- ----------------------------
  27. -- Records of t_flow
  28. -- ----------------------------
  29. INSERT INTO `t_flow` VALUES (1, '请假申请流程', 'applyLeave', 'bpmn/applyLeave.bpmn', 0, '2021-06-15 14:28:49');
  30. -- ----------------------------
  31. -- Table structure for t_site_message
  32. -- ----------------------------
  33. DROP TABLE IF EXISTS `t_site_message`;
  34. CREATE TABLE `t_site_message` (
  35. `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  36. `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户',
  37. `type` int(11) NULL DEFAULT NULL COMMENT '类型:1=待办任务',
  38. `content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '消息内容',
  39. `read_flag` int(11) NULL DEFAULT NULL COMMENT '是否已读:1=已读,0=未读',
  40. `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  41. `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
  42. PRIMARY KEY (`id`) USING BTREE
  43. ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
  44. -- ----------------------------
  45. -- Table structure for t_user
  46. -- ----------------------------
  47. DROP TABLE IF EXISTS `t_user`;
  48. CREATE TABLE `t_user` (
  49. `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID,也是用户ID',
  50. `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户名',
  51. `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '密码',
  52. `enabled` bit(1) NULL DEFAULT b'1' COMMENT '是否生效,1=true,0=false',
  53. `account_non_expired` bit(1) NULL DEFAULT b'1' COMMENT '是否未过期,1=true,0=false',
  54. `account_non_locked` bit(1) NULL DEFAULT b'1' COMMENT '是否未锁定,1=true,0=false',
  55. `credentials_non_expired` bit(1) NULL DEFAULT b'1' COMMENT '证书是否未过期,1=true,0=false',
  56. PRIMARY KEY (`id`) USING BTREE
  57. ) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;
  58. -- ----------------------------
  59. -- Records of t_user
  60. -- ----------------------------
  61. INSERT INTO `t_user` VALUES (1, 'admin', '202cb962ac59075b964b07152d234b70', b'1', b'1', b'1', b'1');
  62. INSERT INTO `t_user` VALUES (2, 'zhangsan', '202cb962ac59075b964b07152d234b70', b'1', b'1', b'1', b'1');
  63. INSERT INTO `t_user` VALUES (3, 'lisi', '202cb962ac59075b964b07152d234b70', b'1', b'1', b'1', b'1');
  64. INSERT INTO `t_user` VALUES (4, 'wangwu', '202cb962ac59075b964b07152d234b70', b'1', b'1', b'1', b'1');
  65. INSERT INTO `t_user` VALUES (5, 'zhuliu', '202cb962ac59075b964b07152d234b70', b'1', b'1', b'1', b'1');

Activiti7 发布正式版之后,它与SpringBoot2.x已经完全支持整合开发。注意,Activiti 默认使用 SpringSecurity 整合开发的。

查看 Activiti 的版本:https://mvnrepository.com/artifact/org.activiti/activiti-engine

在工程的pom.xml文件中引入相关的依赖,其中activiti的依赖是:activiti-spring-boot-starter

完整代码:

1、pom.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <groupId>com.study</groupId>
  7. <artifactId>study-activiti</artifactId>
  8. <version>1.0-SNAPSHOT</version>
  9. <parent>
  10. <groupId>org.springframework.boot</groupId>
  11. <artifactId>spring-boot-starter-parent</artifactId>
  12. <version>2.0.8.RELEASE</version>
  13. </parent>
  14. <dependencies>
  15. <!--Spring boot 集成包-->
  16. <dependency>
  17. <groupId>org.springframework.cloud</groupId>
  18. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  19. </dependency>
  20. <!-- SpringBoot整合Web组件 -->
  21. <dependency>
  22. <groupId>org.springframework.boot</groupId>
  23. <artifactId>spring-boot-starter-web</artifactId>
  24. </dependency>
  25. <dependency>
  26. <groupId>org.springframework.boot</groupId>
  27. <artifactId>spring-boot-starter-test</artifactId>
  28. <scope>test</scope>
  29. </dependency>
  30. <!-- 阿里巴巴的druid数据库连接池 -->
  31. <dependency>
  32. <groupId>com.alibaba</groupId>
  33. <artifactId>druid-spring-boot-starter</artifactId>
  34. <version>1.1.20</version>
  35. </dependency>
  36. <!-- 阿里巴巴 fastjson -->
  37. <dependency>
  38. <groupId>com.alibaba</groupId>
  39. <artifactId>fastjson</artifactId>
  40. <version>1.2.75</version>
  41. </dependency>
  42. <!-- 该 starter 会扫描配置文件中的 DataSource,然后自动创建使用该 DataSource 的 SqlSessionFactoryBean,并注册到 Spring 上下文中 -->
  43. <dependency>
  44. <groupId>org.mybatis.spring.boot</groupId>
  45. <artifactId>mybatis-spring-boot-starter</artifactId>
  46. <version>2.0.0</version>
  47. </dependency>
  48. <!-- 数据库MySQL 依赖包 -->
  49. <dependency>
  50. <groupId>mysql</groupId>
  51. <artifactId>mysql-connector-java</artifactId>
  52. <version>5.1.48</version>
  53. </dependency>
  54. <!-- mybatis 依赖包 -->
  55. <dependency>
  56. <groupId>org.mybatis</groupId>
  57. <artifactId>mybatis</artifactId>
  58. <version>3.4.6</version>
  59. </dependency>
  60. <!-- lombok 依赖 -->
  61. <dependency>
  62. <groupId>org.projectlombok</groupId>
  63. <artifactId>lombok</artifactId>
  64. </dependency>
  65. <!--添加 Activiti工作流的支持-->
  66. <dependency>
  67. <groupId>org.activiti</groupId>
  68. <artifactId>activiti-spring-boot-starter</artifactId>
  69. <version>7.0.0.Beta2</version>
  70. </dependency>
  71. <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
  72. <dependency>
  73. <groupId>commons-io</groupId>
  74. <artifactId>commons-io</artifactId>
  75. <version>2.6</version>
  76. </dependency>
  77. </dependencies>
  78. <dependencyManagement>
  79. <dependencies>
  80. <dependency>
  81. <groupId>org.springframework.cloud</groupId>
  82. <artifactId>spring-cloud-dependencies</artifactId>
  83. <version>Finchley.SR4</version>
  84. <type>pom</type>
  85. <scope>import</scope>
  86. </dependency>
  87. </dependencies>
  88. </dependencyManagement>
  89. </project>

2、bootstrap.yml 配置:

  1. server:
  2. port: 80
  3. # 将SpringBoot项目作为单实例部署调试时,不需要注册到注册中心
  4. eureka:
  5. client:
  6. fetch-registry: false
  7. register-with-eureka: false
  8. spring:
  9. application:
  10. name: activiti-server
  11. datasource:
  12. # 使用阿里巴巴的 druid 数据源
  13. druid:
  14. driver-class-name: com.mysql.jdbc.Driver
  15. url: jdbc:mysql://localhost:3306/study_activiti?characterEncoding=utf-8
  16. username: root
  17. password: root
  18. # Activiti 配置
  19. activiti:
  20. #1.flase:默认值。activiti在启动时,对比数据库表中保存的版本,如果没有表或者版本不匹配,将抛出异常
  21. #2.true: activiti会对数据库中所有表进行更新操作。如果表不存在,则自动创建
  22. #3.create_drop: 在activiti启动时创建表,在关闭时删除表(必须手动关闭引擎,才能删除表)
  23. #4.drop-create: 在activiti启动时删除原来的旧表,然后在创建新表(不需要手动关闭引擎)
  24. database-schema-update: true
  25. #检测历史表是否存在 activiti7 默认没有开启数据库历史记录 启动数据库历史记录
  26. db-history-used: true
  27. #记录历史等级 可配置的历史级别有none, activity, audit, full
  28. #none:不保存任何的历史数据,因此,在流程执行过程中,这是最高效的。
  29. #activity:级别高于none,保存流程实例与流程行为,其他数据不保存。
  30. #audit:除activity级别会保存的数据外,还会保存全部的流程任务及其属性。audit为history的默认值。
  31. #full:保存历史数据的最高级别,除了会保存audit级别的数据外,还会保存其他全部流程相关的细节数据,包括一些流程参数等。
  32. history-level: full
  33. #校验流程文件,默认校验resources下的processes文件夹里的流程文件
  34. check-process-definitions: false

3 mybatis 配置

mybatis:
# Mybatis扫描的mapper文件
mapper-locations: classpath:mapper/*.xml

# 流程定义的配置,实际业务应该是让公司的管理员进行管理流程,然后通过上传文件来处理,目前写固定
process:
flowName: 请假申请流程
key: applyLeave
filePath: bpmn/applyLeave.bpmn
说明:实际项目中,bpmn 文件应该是通过文件服务器上传、读取。这里为了方便,直接读取 resources 的目录。(其实默认是在数据库已经)

4、集成了 SpringSecurity 安全框架

因为 Activiti7 与 SpringBoot整合后,默认情况下,集成了 SpringSecurity 安全框架,这样我们就要去准备 SpringSecurity 整合进来的相关用户权限配置信息。

SpringBoot的依赖包已经将SpringSecurity的依赖包也添加进项目中。

SecurityConfig 配置类:注意要有 @EnableWebSecurity 注解

  1. package com.study.config;
  2. import com.study.handler.MyAuthenticationFailureHandler;
  3. import com.study.handler.MyAuthenticationSuccessHandler;
  4. import com.study.service.MyUserDetailService;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.context.annotation.Bean;
  7. import org.springframework.context.annotation.Configuration;
  8. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
  9. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  10. import org.springframework.security.config.annotation.web.builders.WebSecurity;
  11. import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
  12. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  13. import org.springframework.security.crypto.password.PasswordEncoder;
  14. import org.springframework.util.DigestUtils;
  15. /**
  16. * @author biandan
  17. * @description
  18. * @signature 让天下没有难写的代码
  19. * @create 2021-05-30 下午 9:38
  20. */
  21. @Configuration
  22. @EnableWebSecurity
  23. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  24. @Autowired
  25. private MyAuthenticationSuccessHandler successHandler;
  26. @Autowired
  27. private MyAuthenticationFailureHandler failureHandler;
  28. @Autowired
  29. private MyUserDetailService myUserDetailService;
  30. // 配置认证用户信息和权限
  31. @Override
  32. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  33. //从数据库中获取数据,并校验密码是否一致
  34. auth.userDetailsService(myUserDetailService).passwordEncoder(myPasswordEncoder());
  35. }
  36. /**
  37. * 配置放行的请求
  38. * @param web
  39. * @throws Exception
  40. */
  41. @Override
  42. public void configure(WebSecurity web)throws Exception{
  43. web.ignoring().antMatchers("/css/**");
  44. web.ignoring().antMatchers("/js/**");
  45. web.ignoring().antMatchers("/img/**");
  46. web.ignoring().antMatchers("/plugins/**");
  47. web.ignoring().antMatchers("/login.html");
  48. }
  49. // 配置拦截请求资源
  50. @Override
  51. protected void configure(HttpSecurity http) throws Exception {
  52. //其他任何路径都需要管理员登录
  53. http.authorizeRequests().
  54. antMatchers("/**").
  55. access("hasRole('ADMIN')");
  56. //登录相关配置
  57. http.formLogin()
  58. .loginPage("/login.html") //指定登录地址
  59. .loginProcessingUrl("/login") //指定处理登录的请求地址
  60. .successHandler(successHandler) //登录成功的回调
  61. .failureHandler(failureHandler); //登录失败的回调
  62. //登出配置
  63. http.logout().
  64. logoutUrl("/logout"). //登出地址为/logout
  65. invalidateHttpSession(true); //并且登出后销毁session
  66. //设置用户只允许在一处登录,在其他地方登录则挤掉已登录用户,被挤掉的已登录用户则需要返回/login.html重新登录
  67. http.sessionManagement().maximumSessions(1).expiredUrl("/login.html");
  68. //关闭CSRF安全策略
  69. http.csrf().disable();
  70. //允许跳转显示iframe
  71. http.headers().frameOptions().disable();
  72. //异常处理页面,例如没有权限访问等
  73. http.exceptionHandling().accessDeniedPage("/error.html");
  74. }
  75. //自定义加密方式
  76. @Bean
  77. PasswordEncoder myPasswordEncoder() {
  78. PasswordEncoder passwordEncoder = new PasswordEncoder() {
  79. //重写加密方法
  80. @Override
  81. public String encode(CharSequence charSequence) {
  82. String md5Pwd = DigestUtils.md5DigestAsHex(((String) charSequence).getBytes());
  83. return md5Pwd;
  84. }
  85. /**
  86. * 判断密码是否匹配
  87. *
  88. * @param charSequence 请求的密码
  89. * @param sqlPwd 数据库密码
  90. * @return
  91. */
  92. @Override
  93. public boolean matches(CharSequence charSequence, String sqlPwd) {
  94. String reqPwd = encode(charSequence);
  95. boolean result = reqPwd.equals(sqlPwd);
  96. System.out.println("matches reqPwd=" + reqPwd + ";sqlPwd=" + sqlPwd + ",匹配结果:result=" + result);
  97. return result;//只有返回 true 的情况才会验证成功
  98. }
  99. };
  100. return passwordEncoder;
  101. }
  102. }

我们创建一个工具类:SpringContextUtil,用于获取 Spring 容器的 Bean:

  1. package com.study.util;
  2. import org.springframework.beans.BeansException;
  3. import org.springframework.context.ApplicationContext;
  4. import org.springframework.context.ApplicationContextAware;
  5. import org.springframework.stereotype.Component;
  6. /**
  7. * @author biandan
  8. * @description Spring 上下文工具,可用于获取Spring容器的Bean
  9. * @signature 让天下没有难写的代码
  10. * @create 2021-06-14 下午 9:55
  11. */
  12. @Component
  13. public class SpringContextUtil implements ApplicationContextAware {
  14. private static ApplicationContext applicationContext;
  15. @Override
  16. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  17. this.applicationContext = applicationContext;
  18. }
  19. /**
  20. * 获取 Spring 容器中的 Bean,通过 bean 名称获取
  21. * @param beanName
  22. * @return 返回 Object,需要做强制类型转换
  23. */
  24. public static Object getBean(String beanName){
  25. return applicationContext.getBean(beanName);
  26. }
  27. /**
  28. * 获取 Spring 容器中的 Bean,通过 bean 类型获取
  29. * @param beanClass
  30. * @param <T> 返回指定类型的 bean 实例
  31. * @return
  32. */
  33. public static <T> T getBean(Class<T> beanClass){
  34. return applicationContext.getBean(beanClass);
  35. }
  36. /**
  37. * 获取 Spring 容器中的 Bean,通过 bean 类型获取
  38. * @param beanName 名称
  39. * @param beanClass bean 类型
  40. * @param <T> 返回泛型T,需要强转
  41. * @return
  42. */
  43. public static <T> T getBean(String beanName,Class<T> beanClass){
  44. return applicationContext.getBean(beanName,beanClass);
  45. }
  46. }

5、applyLeave.xml 代码:

  1. <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  2. <definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" 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" xmlns:tns="http://www.activiti.org/test" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" expressionLanguage="http://www.w3.org/1999/XPath" id="m1623737360299" name="" targetNamespace="http://www.activiti.org/test" typeLanguage="http://www.w3.org/2001/XMLSchema">
  3. <process id="applyLeave" isClosed="false" isExecutable="true" name="请假申请流程" processType="None">
  4. <startEvent id="_2" name="流程开始"/>
  5. <endEvent id="_3" name="流程结束">
  6. <extensionElements>
  7. <activiti:executionListener class="com.study.listener.MyExecutionListener" event="end"/>
  8. </extensionElements>
  9. </endEvent>
  10. <userTask activiti:assignee="${assignee0}" activiti:exclusive="true" id="_4" name="创建请假申请">
  11. <extensionElements>
  12. <activiti:taskListener class="com.study.listener.MyTaskListener" event="create"/>
  13. </extensionElements>
  14. </userTask>
  15. <userTask activiti:assignee="${assignee1}" activiti:exclusive="true" id="_5" name="主管审批">
  16. <extensionElements>
  17. <activiti:taskListener class="com.study.listener.MyTaskListener" event="create"/>
  18. </extensionElements>
  19. </userTask>
  20. <sequenceFlow id="_6" sourceRef="_2" targetRef="_4"/>
  21. <sequenceFlow id="_7" sourceRef="_4" targetRef="_5"/>
  22. <userTask activiti:assignee="${assignee2}" activiti:exclusive="true" id="_8" name="经理审批">
  23. <extensionElements>
  24. <activiti:taskListener class="com.study.listener.MyTaskListener" event="create"/>
  25. </extensionElements>
  26. </userTask>
  27. <sequenceFlow id="_9" sourceRef="_5" targetRef="_8"/>
  28. <exclusiveGateway gatewayDirection="Unspecified" id="_10" name="排他网关"/>
  29. <sequenceFlow id="_11" sourceRef="_8" targetRef="_10"/>
  30. <sequenceFlow id="_12" sourceRef="_10" targetRef="_3">
  31. <conditionExpression xsi:type="tFormalExpression"><![CDATA[${applyLeave.day<=3}]]></conditionExpression>
  32. </sequenceFlow>
  33. <userTask activiti:assignee="${assignee3}" activiti:exclusive="true" id="_13" name="人资审批">
  34. <extensionElements>
  35. <activiti:taskListener class="com.study.listener.MyTaskListener" event="create"/>
  36. </extensionElements>
  37. </userTask>
  38. <sequenceFlow id="_14" sourceRef="_10" targetRef="_13">
  39. <conditionExpression xsi:type="tFormalExpression"><![CDATA[${applyLeave.day>3}]]></conditionExpression>
  40. </sequenceFlow>
  41. <sequenceFlow id="_15" sourceRef="_13" targetRef="_3"/>
  42. </process>
  43. <bpmndi:BPMNDiagram documentation="background=#FFFFFF;count=1;horizontalcount=1;orientation=0;width=842.4;height=1195.2;imageableWidth=832.4;imageableHeight=1185.2;imageableX=5.0;imageableY=5.0" id="Diagram-_1" name="New Diagram">
  44. <bpmndi:BPMNPlane bpmnElement="applyLeave">
  45. <bpmndi:BPMNShape bpmnElement="_2" id="Shape-_2">
  46. <omgdc:Bounds height="32.0" width="32.0" x="285.0" y="20.0"/>
  47. <bpmndi:BPMNLabel>
  48. <omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
  49. </bpmndi:BPMNLabel>
  50. </bpmndi:BPMNShape>
  51. <bpmndi:BPMNShape bpmnElement="_3" id="Shape-_3">
  52. <omgdc:Bounds height="32.0" width="32.0" x="285.0" y="500.0"/>
  53. <bpmndi:BPMNLabel>
  54. <omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
  55. </bpmndi:BPMNLabel>
  56. </bpmndi:BPMNShape>
  57. <bpmndi:BPMNShape bpmnElement="_4" id="Shape-_4">
  58. <omgdc:Bounds height="55.0" width="85.0" x="260.0" y="90.0"/>
  59. <bpmndi:BPMNLabel>
  60. <omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
  61. </bpmndi:BPMNLabel>
  62. </bpmndi:BPMNShape>
  63. <bpmndi:BPMNShape bpmnElement="_5" id="Shape-_5">
  64. <omgdc:Bounds height="55.0" width="85.0" x="260.0" y="175.0"/>
  65. <bpmndi:BPMNLabel>
  66. <omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
  67. </bpmndi:BPMNLabel>
  68. </bpmndi:BPMNShape>
  69. <bpmndi:BPMNShape bpmnElement="_8" id="Shape-_8">
  70. <omgdc:Bounds height="55.0" width="85.0" x="260.0" y="265.0"/>
  71. <bpmndi:BPMNLabel>
  72. <omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
  73. </bpmndi:BPMNLabel>
  74. </bpmndi:BPMNShape>
  75. <bpmndi:BPMNShape bpmnElement="_10" id="Shape-_10" isMarkerVisible="false">
  76. <omgdc:Bounds height="32.0" width="32.0" x="285.0" y="355.0"/>
  77. <bpmndi:BPMNLabel>
  78. <omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
  79. </bpmndi:BPMNLabel>
  80. </bpmndi:BPMNShape>
  81. <bpmndi:BPMNShape bpmnElement="_13" id="Shape-_13">
  82. <omgdc:Bounds height="55.0" width="85.0" x="385.0" y="415.0"/>
  83. <bpmndi:BPMNLabel>
  84. <omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
  85. </bpmndi:BPMNLabel>
  86. </bpmndi:BPMNShape>
  87. <bpmndi:BPMNEdge bpmnElement="_12" id="BPMNEdge__12" sourceElement="_10" targetElement="_3">
  88. <omgdi:waypoint x="301.0" y="387.0"/>
  89. <omgdi:waypoint x="301.0" y="500.0"/>
  90. <bpmndi:BPMNLabel>
  91. <omgdc:Bounds height="-6.0" width="0.0" x="0.0" y="-56.0"/>
  92. </bpmndi:BPMNLabel>
  93. </bpmndi:BPMNEdge>
  94. <bpmndi:BPMNEdge bpmnElement="_15" id="BPMNEdge__15" sourceElement="_13" targetElement="_3">
  95. <omgdi:waypoint x="426.0" y="470.0"/>
  96. <omgdi:waypoint x="426.0" y="515.0"/>
  97. <omgdi:waypoint x="316.9687194226713" y="515.0"/>
  98. <bpmndi:BPMNLabel>
  99. <omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
  100. </bpmndi:BPMNLabel>
  101. </bpmndi:BPMNEdge>
  102. <bpmndi:BPMNEdge bpmnElement="_14" id="BPMNEdge__14" sourceElement="_10" targetElement="_13">
  103. <omgdi:waypoint x="317.0" y="371.0"/>
  104. <omgdi:waypoint x="425.0" y="415.0"/>
  105. <omgdi:waypoint x="425.0" y="415.0"/>
  106. <bpmndi:BPMNLabel>
  107. <omgdc:Bounds height="0.0" width="10.0" x="0.0" y="11.0"/>
  108. </bpmndi:BPMNLabel>
  109. </bpmndi:BPMNEdge>
  110. <bpmndi:BPMNEdge bpmnElement="_6" id="BPMNEdge__6" sourceElement="_2" targetElement="_4">
  111. <omgdi:waypoint x="301.0" y="52.0"/>
  112. <omgdi:waypoint x="301.0" y="90.0"/>
  113. <bpmndi:BPMNLabel>
  114. <omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
  115. </bpmndi:BPMNLabel>
  116. </bpmndi:BPMNEdge>
  117. <bpmndi:BPMNEdge bpmnElement="_7" id="BPMNEdge__7" sourceElement="_4" targetElement="_5">
  118. <omgdi:waypoint x="302.5" y="145.0"/>
  119. <omgdi:waypoint x="302.5" y="175.0"/>
  120. <bpmndi:BPMNLabel>
  121. <omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
  122. </bpmndi:BPMNLabel>
  123. </bpmndi:BPMNEdge>
  124. <bpmndi:BPMNEdge bpmnElement="_9" id="BPMNEdge__9" sourceElement="_5" targetElement="_8">
  125. <omgdi:waypoint x="302.5" y="230.0"/>
  126. <omgdi:waypoint x="302.5" y="265.0"/>
  127. <bpmndi:BPMNLabel>
  128. <omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
  129. </bpmndi:BPMNLabel>
  130. </bpmndi:BPMNEdge>
  131. <bpmndi:BPMNEdge bpmnElement="_11" id="BPMNEdge__11" sourceElement="_8" targetElement="_10">
  132. <omgdi:waypoint x="301.0" y="320.0"/>
  133. <omgdi:waypoint x="301.0" y="355.0"/>
  134. <bpmndi:BPMNLabel>
  135. <omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
  136. </bpmndi:BPMNLabel>
  137. </bpmndi:BPMNEdge>
  138. </bpmndi:BPMNPlane>
  139. </bpmndi:BPMNDiagram>
  140. </definitions>

流程图:

image.png

项目演示效果:http://127.0.0.1/

1、使用其中一个账号(目前都是管理员权限)在【流程管理】菜单里,部署【请假申请流程】。

image.png

2、然后切换到 lisi 的账号进行审批

image.png

3、然后切换到 wangwu 的账号审批。因为请假天数小于 3 天,所以直接审批结束。
image.png

4、最后切换回 zhangsan 的账号查看。

image.png

OK,Activiti 学习到这。

[

](https://blog.csdn.net/BiandanLoveyou/article/details/117909703)