系列学习 SpringBoot + Activiti7 工作流之第 7 篇 —— 整合 SpringSecurity 项目实战(完结)

查看之前的博客可以点击顶部的【分类专栏】

 

这篇博客将利用之前所学的零散知识,整合成一个能跑起来的完整项目!

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

 

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

 

说明:

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

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

DROP TABLE IF EXISTS `t_apply_leave`;
CREATE TABLE `t_apply_leave`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `apply_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '请假标题',
  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户',
  `day` double(4, 1) NULL DEFAULT NULL COMMENT '请假天数,保留1位小数',
  `begin_date` date NULL DEFAULT NULL COMMENT '开始日期',
  `end_date` date NULL DEFAULT NULL COMMENT '结束日期',
  `reason` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '请假事由',
  `state` int(11) NULL DEFAULT NULL COMMENT '状态:0=开始录入,1=开始审批,2=审批完成',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for t_flow
-- ----------------------------
DROP TABLE IF EXISTS `t_flow`;
CREATE TABLE `t_flow`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `flow_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '部署的名称',
  `flow_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '部署的key',
  `file_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文件路径',
  `state` int(11) NULL DEFAULT NULL COMMENT '部署状态:1=已部署,0=未部署',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '部署时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_flow
-- ----------------------------
INSERT INTO `t_flow` VALUES (1, '请假申请流程', 'applyLeave', 'bpmn/applyLeave.bpmn', 0, '2021-06-15 14:28:49');

-- ----------------------------
-- Table structure for t_site_message
-- ----------------------------
DROP TABLE IF EXISTS `t_site_message`;
CREATE TABLE `t_site_message`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户',
  `type` int(11) NULL DEFAULT NULL COMMENT '类型:1=待办任务',
  `content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '消息内容',
  `read_flag` int(11) NULL DEFAULT NULL COMMENT '是否已读:1=已读,0=未读',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID,也是用户ID',
  `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户名',
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '密码',
  `enabled` bit(1) NULL DEFAULT b'1' COMMENT '是否生效,1=true,0=false',
  `account_non_expired` bit(1) NULL DEFAULT b'1' COMMENT '是否未过期,1=true,0=false',
  `account_non_locked` bit(1) NULL DEFAULT b'1' COMMENT '是否未锁定,1=true,0=false',
  `credentials_non_expired` bit(1) NULL DEFAULT b'1' COMMENT '证书是否未过期,1=true,0=false',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_user
-- ----------------------------
INSERT INTO `t_user` VALUES (1, 'admin', '202cb962ac59075b964b07152d234b70', b'1', b'1', b'1', b'1');
INSERT INTO `t_user` VALUES (2, 'zhangsan', '202cb962ac59075b964b07152d234b70', b'1', b'1', b'1', b'1');
INSERT INTO `t_user` VALUES (3, 'lisi', '202cb962ac59075b964b07152d234b70', b'1', b'1', b'1', b'1');
INSERT INTO `t_user` VALUES (4, 'wangwu', '202cb962ac59075b964b07152d234b70', b'1', b'1', b'1', b'1');
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

完整代码:

<?xml version="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.0</modelVersion>

    <groupId>com.study</groupId>
    <artifactId>study-activiti</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.8.RELEASE</version>
    </parent>

    <dependencies>
        <!--Spring boot 集成包-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- SpringBoot整合Web组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- 阿里巴巴的druid数据库连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.20</version>
        </dependency>
        <!-- 阿里巴巴 fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.75</version>
        </dependency>
        <!-- 该 starter 会扫描配置文件中的 DataSource,然后自动创建使用该 DataSource 的 SqlSessionFactoryBean,并注册到 Spring 上下文中 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>
        <!-- 数据库MySQL 依赖包 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.48</version>
        </dependency>
        <!-- mybatis 依赖包 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.6</version>
        </dependency>
        <!-- lombok 依赖 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--添加 Activiti工作流的支持-->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring-boot-starter</artifactId>
            <version>7.0.0.Beta2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Finchley.SR4</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

bootstrap.yml 配置:

server:
  port: 80

# 将SpringBoot项目作为单实例部署调试时,不需要注册到注册中心
eureka:
  client:
    fetch-registry: false
    register-with-eureka: false

spring:
  application:
    name: activiti-server
  datasource:
    # 使用阿里巴巴的 druid 数据源
    druid:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/study_activiti?characterEncoding=utf-8
      username: root
      password: root
  # Activiti 配置
  activiti:
    #1.flase:默认值。activiti在启动时,对比数据库表中保存的版本,如果没有表或者版本不匹配,将抛出异常
    #2.true: activiti会对数据库中所有表进行更新操作。如果表不存在,则自动创建
    #3.create_drop: 在activiti启动时创建表,在关闭时删除表(必须手动关闭引擎,才能删除表)
    #4.drop-create: 在activiti启动时删除原来的旧表,然后在创建新表(不需要手动关闭引擎)
    database-schema-update: true

    #检测历史表是否存在 activiti7 默认没有开启数据库历史记录 启动数据库历史记录
    db-history-used: true

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

    #校验流程文件,默认校验resources下的processes文件夹里的流程文件
    check-process-definitions: false

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

# 流程定义的配置,实际业务应该是让公司的管理员进行管理流程,然后通过上传文件来处理,目前写固定
process:
  flowName: 请假申请流程
  key: applyLeave
  filePath: bpmn/applyLeave.bpmn
  

说明:实际项目中,bpmn 文件应该是通过文件服务器上传、读取。这里为了方便,直接读取 resources 的目录。(其实默认是在数据库已经)

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

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

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

package com.study.config;

import com.study.handler.MyAuthenticationFailureHandler;
import com.study.handler.MyAuthenticationSuccessHandler;
import com.study.service.MyUserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.DigestUtils;

/**
 * @author biandan
 * @description
 * @signature 让天下没有难写的代码
 * @create 2021-05-30 下午 9:38
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyAuthenticationSuccessHandler successHandler;

    @Autowired
    private MyAuthenticationFailureHandler failureHandler;

    @Autowired
    private MyUserDetailService myUserDetailService;

    // 配置认证用户信息和权限
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //从数据库中获取数据,并校验密码是否一致
        auth.userDetailsService(myUserDetailService).passwordEncoder(myPasswordEncoder());
    }

    /**
     * 配置放行的请求
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web)throws Exception{
        web.ignoring().antMatchers("/css/**");
        web.ignoring().antMatchers("/js/**");
        web.ignoring().antMatchers("/img/**");
        web.ignoring().antMatchers("/plugins/**");
        web.ignoring().antMatchers("/login.html");
    }

    // 配置拦截请求资源
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //其他任何路径都需要管理员登录
        http.authorizeRequests().
                antMatchers("/**").
                access("hasRole('ADMIN')");

        //登录相关配置
        http.formLogin()
                .loginPage("/login.html")   //指定登录地址
                .loginProcessingUrl("/login")       //指定处理登录的请求地址
                .successHandler(successHandler) //登录成功的回调
                .failureHandler(failureHandler); //登录失败的回调
        //登出配置
        http.logout().
                logoutUrl("/logout").           //登出地址为/logout
                invalidateHttpSession(true);    //并且登出后销毁session

        //设置用户只允许在一处登录,在其他地方登录则挤掉已登录用户,被挤掉的已登录用户则需要返回/login.html重新登录
        http.sessionManagement().maximumSessions(1).expiredUrl("/login.html");

        //关闭CSRF安全策略
        http.csrf().disable();

        //允许跳转显示iframe
        http.headers().frameOptions().disable();

        //异常处理页面,例如没有权限访问等
        http.exceptionHandling().accessDeniedPage("/error.html");
    }

    //自定义加密方式
    @Bean
    PasswordEncoder myPasswordEncoder() {
        PasswordEncoder passwordEncoder = new PasswordEncoder() {
            //重写加密方法
            @Override
            public String encode(CharSequence charSequence) {
                String md5Pwd = DigestUtils.md5DigestAsHex(((String) charSequence).getBytes());
                return md5Pwd;
            }

            /**
             * 判断密码是否匹配
             *
             * @param charSequence 请求的密码
             * @param sqlPwd       数据库密码
             * @return
             */
            @Override
            public boolean matches(CharSequence charSequence, String sqlPwd) {
                String reqPwd = encode(charSequence);
                boolean result = reqPwd.equals(sqlPwd);
                System.out.println("matches reqPwd=" + reqPwd + ";sqlPwd=" + sqlPwd + ",匹配结果:result=" + result);
                return result;//只有返回 true 的情况才会验证成功
            }
        };
        return passwordEncoder;
    }
}

 

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

package com.study.util;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * @author biandan
 * @description Spring 上下文工具,可用于获取Spring容器的Bean
 * @signature 让天下没有难写的代码
 * @create 2021-06-14 下午 9:55
 */
@Component
public class SpringContextUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 获取 Spring 容器中的 Bean,通过 bean 名称获取
     * @param beanName
     * @return 返回 Object,需要做强制类型转换
     */
    public static Object getBean(String beanName){
        return applicationContext.getBean(beanName);
    }

    /**
     * 获取 Spring 容器中的 Bean,通过 bean 类型获取
     * @param beanClass
     * @param <T> 返回指定类型的 bean 实例
     * @return
     */
    public static <T> T getBean(Class<T> beanClass){
        return applicationContext.getBean(beanClass);
    }

    /**
     * 获取 Spring 容器中的 Bean,通过 bean 类型获取
     * @param beanName 名称
     * @param beanClass bean 类型
     * @param <T> 返回泛型T,需要强转
     * @return
     */
    public static <T> T getBean(String beanName,Class<T> beanClass){
        return applicationContext.getBean(beanName,beanClass);
    }


}

applyLeave.xml 代码:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<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">
  <process id="applyLeave" isClosed="false" isExecutable="true" name="请假申请流程" processType="None">
    <startEvent id="_2" name="流程开始"/>
    <endEvent id="_3" name="流程结束">
      <extensionElements>
        <activiti:executionListener class="com.study.listener.MyExecutionListener" event="end"/>
      </extensionElements>
    </endEvent>
    <userTask activiti:assignee="${assignee0}" activiti:exclusive="true" id="_4" name="创建请假申请">
      <extensionElements>
        <activiti:taskListener class="com.study.listener.MyTaskListener" event="create"/>
      </extensionElements>
    </userTask>
    <userTask activiti:assignee="${assignee1}" activiti:exclusive="true" id="_5" name="主管审批">
      <extensionElements>
        <activiti:taskListener class="com.study.listener.MyTaskListener" event="create"/>
      </extensionElements>
    </userTask>
    <sequenceFlow id="_6" sourceRef="_2" targetRef="_4"/>
    <sequenceFlow id="_7" sourceRef="_4" targetRef="_5"/>
    <userTask activiti:assignee="${assignee2}" activiti:exclusive="true" id="_8" name="经理审批">
      <extensionElements>
        <activiti:taskListener class="com.study.listener.MyTaskListener" event="create"/>
      </extensionElements>
    </userTask>
    <sequenceFlow id="_9" sourceRef="_5" targetRef="_8"/>
    <exclusiveGateway gatewayDirection="Unspecified" id="_10" name="排他网关"/>
    <sequenceFlow id="_11" sourceRef="_8" targetRef="_10"/>
    <sequenceFlow id="_12" sourceRef="_10" targetRef="_3">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${applyLeave.day<=3}]]></conditionExpression>
    </sequenceFlow>
    <userTask activiti:assignee="${assignee3}" activiti:exclusive="true" id="_13" name="人资审批">
      <extensionElements>
        <activiti:taskListener class="com.study.listener.MyTaskListener" event="create"/>
      </extensionElements>
    </userTask>
    <sequenceFlow id="_14" sourceRef="_10" targetRef="_13">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${applyLeave.day>3}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="_15" sourceRef="_13" targetRef="_3"/>
  </process>
  <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">
    <bpmndi:BPMNPlane bpmnElement="applyLeave">
      <bpmndi:BPMNShape bpmnElement="_2" id="Shape-_2">
        <omgdc:Bounds height="32.0" width="32.0" x="285.0" y="20.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="_3" id="Shape-_3">
        <omgdc:Bounds height="32.0" width="32.0" x="285.0" y="500.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="_4" id="Shape-_4">
        <omgdc:Bounds height="55.0" width="85.0" x="260.0" y="90.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="_5" id="Shape-_5">
        <omgdc:Bounds height="55.0" width="85.0" x="260.0" y="175.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="_8" id="Shape-_8">
        <omgdc:Bounds height="55.0" width="85.0" x="260.0" y="265.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="_10" id="Shape-_10" isMarkerVisible="false">
        <omgdc:Bounds height="32.0" width="32.0" x="285.0" y="355.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="_13" id="Shape-_13">
        <omgdc:Bounds height="55.0" width="85.0" x="385.0" y="415.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="_12" id="BPMNEdge__12" sourceElement="_10" targetElement="_3">
        <omgdi:waypoint x="301.0" y="387.0"/>
        <omgdi:waypoint x="301.0" y="500.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="-6.0" width="0.0" x="0.0" y="-56.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="_15" id="BPMNEdge__15" sourceElement="_13" targetElement="_3">
        <omgdi:waypoint x="426.0" y="470.0"/>
        <omgdi:waypoint x="426.0" y="515.0"/>
        <omgdi:waypoint x="316.9687194226713" y="515.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="_14" id="BPMNEdge__14" sourceElement="_10" targetElement="_13">
        <omgdi:waypoint x="317.0" y="371.0"/>
        <omgdi:waypoint x="425.0" y="415.0"/>
        <omgdi:waypoint x="425.0" y="415.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="0.0" width="10.0" x="0.0" y="11.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="_6" id="BPMNEdge__6" sourceElement="_2" targetElement="_4">
        <omgdi:waypoint x="301.0" y="52.0"/>
        <omgdi:waypoint x="301.0" y="90.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="_7" id="BPMNEdge__7" sourceElement="_4" targetElement="_5">
        <omgdi:waypoint x="302.5" y="145.0"/>
        <omgdi:waypoint x="302.5" y="175.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="_9" id="BPMNEdge__9" sourceElement="_5" targetElement="_8">
        <omgdi:waypoint x="302.5" y="230.0"/>
        <omgdi:waypoint x="302.5" y="265.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="_11" id="BPMNEdge__11" sourceElement="_8" targetElement="_10">
        <omgdi:waypoint x="301.0" y="320.0"/>
        <omgdi:waypoint x="301.0" y="355.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

 

流程图:

 

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

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

 

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

 

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

 

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

 

OK,Activiti 学习到这。

 

本篇博客以及之前的博客的代码地址:https://pan.baidu.com/s/1RNgxtVgKkv1pnIfBjyZWMA   提取码:pyuy

 

 

 

 

 

  • 4
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值