<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.dmg</groupId>
<artifactId>flow02</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>flow02</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
<flowable.version>6.7.2</flowable.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.7</version>
</dependency>
<!-- 添加MyBatisPlus的依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!-- MySQL数据 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<!-- druid 连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.14</version>
</dependency>
<!-- MyBatisPlus 代码生成器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!-- JSR 303 的依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>${flowable.version}</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>com.dmg.Flow02Application</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
application.properties
# 应用名称
spring.application.name=flow02
# 应用服务 WEB 访问端口
server.port=8080
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
#&nullCatalogMeansCurrent=true 一定要有 否则报错
spring.datasource.url=jdbc:mysql://localhost:3306/flowable?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=true&nullCatalogMeansCurrent=true
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
mybatis-plus.mapper-locations=classpath:mapper/*.xml
# 控制台打印sql语句
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# Flowable相关的日志打印
logging.level.org.flowable.engine.impl.persistence.entity.*=debug
logging.level.org.flowable.task.service.impl.persistence.entity.*=debug
# Flowable的配置
#关闭定时任务JOB 定时事件使用
flowable.async-executor-activate=true
#当Flowable发现库与数据库表结构不一致时,会自动将数据库表结构升级至新版本
flowable.database-schema-update=true
创建工作流工厂类,管理所有工作流服务
package com.dmg.service;
import org.flowable.engine.*;
import org.flowable.task.service.HistoricTaskService;
import org.flowable.variable.service.HistoricVariableService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 工作流服务的工厂类 被其他类 所继承
* 这里使用受保护的 就是可以被所继承的类使用
*/
@Component
public class FlowServiceFactory {
@Autowired
protected IdentityService identityService;
@Autowired
protected ManagementService managementService;
@Autowired
protected TaskService taskService;
@Autowired
protected ProcessEngine processEngine;
@Autowired
protected RuntimeService runtimeService;
@Autowired
protected RepositoryService repositoryService;
@Autowired
protected HistoryService historyService;
}
创建返回全局json实体
package com.dmg.common;
/**
* 返回全局json实体
* @param <T>
*/
public class Result<T> {
/**
* 错误码,表示一种错误类型
* 请求成功,状态码为200
*/
private int code;
/**
* 对错误代码的详细解释
*/
private String message;
/**
* 返回的结果包装在value中,value可以是单个对象
*/
private T data;
public Result() {
}
public Result(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public static Result success(Object object) {
Result apiResult = new Result();
apiResult.setData(object);
apiResult.setCode(200);
apiResult.setMessage("请求成功");
return apiResult;
}
public static Result success() {
return success(null);
}
public static <T> Result buildResult(Integer code, String message, T data) {
Result result = new Result();
result.setCode(code);
result.setMessage(message);
result.setData(data);
return result;
}
}
创建FlowService
package com.dmg.service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
public interface FlowService {
public void deploy(String name, MultipartFile file) throws IOException;
}
package com.dmg.config;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
import org.springframework.context.annotation.Configuration;
/**
* 防止流程图中文乱码
*/
@Configuration
public class FlowableConfig implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> {
@Override
public void configure(SpringProcessEngineConfiguration conn) {
//设置活动字体名称
conn.setActivityFontName("宋体");
//设置标签字体名称
conn.setLabelFontName("宋体");
//设置注释字体名称
conn.setAnnotationFontName("宋体");
}
}
创建FlowServiceImpl
package com.dmg.service.impl;
/**
* 流程定义服务实现类
*/
@Service
public class FlowServiceImpl extends FlowServiceFactory implements FlowService {
/**
* 部署
*/
@Override
public void deploy(String name, MultipartFile file) throws IOException {
DeploymentBuilder deploymentBuilder=repositoryService.createDeployment();
deploymentBuilder.name(name);
String fileName=file.getOriginalFilename();
if(fileName.endsWith(".bpmn20.xml") || fileName.endsWith(".bpmn")){
deploymentBuilder.addInputStream(file.getOriginalFilename(),file.getInputStream());
}else if(fileName.endsWith(".bar") || fileName.endsWith(".zip")){
deploymentBuilder.addZipInputStream(new ZipInputStream(file.getInputStream()));
}else {
throw new RuntimeException("文件类型不是 .bpmn20.xml 或者 .bpmn 或者 .bar 或者.zip");
}
//部署成功后 act_re_deployment,act_re_procdef,act_ge_bytearray 表就有数据了
deploymentBuilder.deploy();
}
}
创建ProcessDefinitionController
package com.dmg.controller;
import java.io.IOException;
/**
* 流程定义控制层
*/
@RestController
public class ProcessDefinitionController {
@Autowired
private FlowService flowService;
/**
* 部署流程 文件类型 .bpmn20.xml 或者 .bpmn 或者 .bar 或者.zip
*/
@PostMapping("deploy")
public Result deploy(@RequestParam("name") String name,@RequestParam("file") MultipartFile file){
try {
flowService.deploy(name,file);
} catch (IOException e) {
e.printStackTrace();
}
return Result.success();
}
}
package com.dmg;
@MapperScan("com.dmg.mapper")
@SpringBootApplication
public class Flow02Application {
public static void main(String[] args) {
SpringApplication.run(Flow02Application.class, args);
}
}
先在数据库创建好数据库,然后在启动项目
我们先在flowable-ui创建好流程图
key要唯一
这个是开始事件
这个是用户任务
这个是结束事件 就是流程走完了
这里是网关
点击箭头就可以指向其他地方,放入到对方里面,就自动联线了
下面就是我们的完整流程图了
字符串的表达式一定要加单引号,例如${flag=='true'}
注意在流条件,表达式里面不能写其他的字符例如 通过${flag=='true'},要把通过去掉,否则
UelExpressionCondition这个类会抛出异常
点击保存
选择流程图
点击下载
然后把文件拿到,上传,进行部署
访问http://localhost:8080/deploy
可以看到部署成功了,我们在看下数据库
先创建请假单表
CREATE TABLE `tb_qj` (
`id` varchar(50) NOT NULL COMMENT '主键',
`name` varchar(50) DEFAULT NULL COMMENT '申请单名称',
`day` double DEFAULT NULL COMMENT '请假天数',
`create_by` varchar(50) DEFAULT NULL COMMENT '创建人账号',
`create_by_name` varchar(50) DEFAULT NULL COMMENT '创建人名称',
`instance_id` varchar(50) DEFAULT NULL COMMENT '流程实例id',
`approval_status` varchar(10) DEFAULT NULL COMMENT '审批状态 1:未提交 2:审批中 3:审批通过 ',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='请假表';
创建实体类
package com.dmg.entity;
import java.util.Date;
import lombok.Data;
/**
* 请假表
*/
@Data
@TableName("tb_qj")
public class Qj {
/**
* 主键
*/
private String id;
/**
* 申请单名称
*/
private String name;
/**
* 请假天数
*/
private Double day;
/**
* 创建人账号
*/
private String createBy;
/**
* 创建人名称
*/
private String createByName;
/**
* 流程实例id
*/
private String instanceId;
/**
* 审批状态 1:未提交 2:审批中 3:审批通过
*/
private String approvalStatus;
/**
* 创建时间
*/
private Date createTime;
}
创建mapper接口
public interface QjMapper extends BaseMapper<Qj> {
}
我们在看下流程定义和部署的信息
package com.dmg.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 配置这个插件 mybatis-plus分页才好使
*/
@Configuration
//指定扫描目录
@MapperScan("com.dmg.mapper")
public class MybatisPlusConfig{
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//注意使用哪种数据库
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
package com.dmg.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
/**
* 流程定义 部署 vo类
*/
@Data
public class ProcessDefinitionVo {
// 流程定义ID
private String processDefinitionId;
// 流程定义名称
private String processDefinitionName;
// 流程定义key
private String processDefinitionKey;
// 租户编号
private String tenantId;
// 部署编号
private String deployId;
// 部署的时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date deployTime;
// 是否挂起 1正常 2挂起
private Integer suspensionState;
// 流程定义的版本
private int version;
// 资源名称
private String resourceName;
// dgrm资源名称
private String dgrmResourceName;
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dmg.mapper.QjMapper">
<select id="getProcessDefinitionPage" resultType="com.dmg.vo.ProcessDefinitionVo">
SELECT
a.ID_ processDefinitionId,
a.KEY_ processDefinitionKey,
a.NAME_ processDefinitionName,
a.DEPLOYMENT_ID_ deployId,
a.VERSION_ version,
a.RESOURCE_NAME_ resourceName,
a.DGRM_RESOURCE_NAME_ dgrmResourceName,
a.SUSPENSION_STATE_ suspensionState,
a.TENANT_ID_ tenantId,
b.DEPLOY_TIME_ deployTime
FROM
act_re_procdef as a
LEFT JOIN act_re_deployment as b on a.DEPLOYMENT_ID_=b.ID_
ORDER BY b.DEPLOY_TIME_ desc
</select>
</mapper>
package com.dmg.mapper;
public interface QjMapper extends BaseMapper<Qj> {
/**
* 分页流程定义 部署信息
* @param page
* @return
*/
IPage<ProcessDefinitionVo>getProcessDefinitionPage(Page<ProcessDefinitionVo>page);
}
package com.dmg.service;
public interface FlowService {
public IPage<ProcessDefinitionVo> getProcessDefinitionPage(int current, int size);
}
package com.dmg.service.impl;
/**
* 流程定义服务实现类
*/
@Service
public class FlowServiceImpl extends FlowServiceFactory implements FlowService {
@Autowired
private QjMapper qjMapper;
/**
* 分页查询流程定义信息
* @param current 当前页
* @param size 每页条数
*/
@Override
public IPage<ProcessDefinitionVo> getProcessDefinitionPage(int current,int size) {
Page<ProcessDefinitionVo>page=new Page<>(current,size);
IPage<ProcessDefinitionVo> processDefinitionVoIPage = qjMapper.getProcessDefinitionPage(page);
return processDefinitionVoIPage;
}
}
package com.dmg.controller;
import java.io.IOException;
/**
* 流程定义控制层
*/
@RestController
public class ProcessDefinitionController {
@Autowired
private FlowService flowService;
/**
* 分页查询流程定义 部署信息
*/
@PostMapping("getProcessDefinitionPage")
public Result getProcessDefinitionPage(@RequestBody PageReq req){
IPage<ProcessDefinitionVo> processDefinitionPage = flowService.getProcessDefinitionPage(req.getCurrent()
, req.getSize());
return Result.success(processDefinitionPage);
}
}
package com.dmg.vo.req;
import lombok.Data;
@Data
public class PageReq {
/**
* 当前页
*/
int current;
/**
* 每页条数
*/
int size;
}
http://localhost:8080/getProcessDefinitionPage
{
"current":"1",
"size": 2
}
我们来看下效果
我们也可以对当前流程进行挂载和激活
挂载后,无法在启动流程实例
只有正常的情况下才能往下走
package com.dmg.vo.req;
import lombok.Data;
@Data
public class FlowReq extends PageReq{
/**
* 流程定义名称
*/
private String processDefinitionName;
/**
* 流程定义key
*/
private String processDefinitionKey;
/**
* 任务id
*/
private String taskId;
/**
* 当前人账号
*/
private String account;
/**
* 流程定义id
*/
private String processDefinitionId;
}
package com.dmg.service.impl;
/**
* 流程定义服务实现类
*/
@Service
public class FlowServiceImpl extends FlowServiceFactory implements FlowService {
/**
* 挂载/激活 流程定义
* @param req
*/
@Override
public void mountActivation(FlowReq req) {
ProcessDefinition processDefinition = repositoryService.
createProcessDefinitionQuery().processDefinitionId(req.getProcessDefinitionId()).singleResult();
if(processDefinition.isSuspended()){
//如果是暂停 那么激活他
repositoryService.activateProcessDefinitionById(req.getProcessDefinitionId());
}else {
//如果是正常的 那么挂起他
repositoryService.suspendProcessDefinitionById(req.getProcessDefinitionId());
}
}
}
package com.dmg.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.dmg.vo.ProcessDefinitionVo;
import com.dmg.vo.req.FlowReq;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.Map;
public interface FlowService {
public void mountActivation(FlowReq req);
}
package com.dmg.controller;
/**
* 流程定义控制层
*/
@RestController
public class ProcessDefinitionController {
@Autowired
private FlowService flowService;
/**
* 挂载/激活 流程定义
*/
@PostMapping("mountActivation")
public Result mountActivation(@RequestBody FlowReq req){
flowService.mountActivation(req);
return Result.success();
}
}
我们在来看下结果
http://localhost:8080/mountActivation
{
"processDefinitionId":"qj:2:e60a4c1e-6cb8-11ed-b720-005056c00008"
}
我们可以看到act_re_procdef表的SUSPENSION_STATE_字段已经变成了2,就是挂起状态了
这时候 我们启动一个流程实例来看看 他是是报错
package com.dmg.service;
public interface FlowService {
public String startProcessInstance(String processDefinitionKey);
}
package com.dmg.service.impl;
/**
* 流程定义服务实现类
*/
@Service
public class FlowServiceImpl extends FlowServiceFactory implements FlowService {
/**
* 启动流程实例,返回流程实例id
* @param processDefinitionKey 流程定义key
*/
@Override
public String startProcessInstance(String processDefinitionKey){
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefinitionKey);
if(processInstance==null){
throw new RuntimeException("启动流程实例失败");
}
//返回流程实例id
return processInstance.getId();
}
}
package com.dmg.controller;
/**
* 流程定义控制层
*/
@RestController
public class ProcessDefinitionController {
@Autowired
private FlowService flowService;
/**
* 启动流程实例
*/
@PostMapping("startProcessInstance")
public Result startProcessInstance(@RequestBody FlowReq req){
String s = flowService.startProcessInstance(req.getProcessDefinitionKey());
return Result.success(s);
}
}
我们来看下效果
http://localhost:8080/startProcessInstance
{
"processDefinitionKey":"qj"
}
我这里启动流程实例,每次都是最新的版本启动的,一定要注意
可以看到当前流程是挂起的,不能启动流程实例
我们在吧流程定义激活,然后在启动下流程实例
可以看到 已经启动成功了
act_ru_task 运行时任务表已经有了数据,就是这个 提交
我们也可以部署bpmn.js传过来的xml文件的数据
注意这里的name后面必须以.bpmn结尾,否则act_re_procdef(已部署的流程定义表)没有数据
/**
* 部署xml
*/
@PostMapping(value = "deployXml")
public String deployXml(@RequestParam("name")String name,@RequestParam("xml") String xml) throws IOException {
Deployment deploy = repositoryService.createDeployment()
//name必须以xxx.bpmn结尾
.addInputStream(name+".bpmn",new ByteArrayInputStream(xml.getBytes()))
.name(name)
.deploy();
return deploy.getId();
}
结果如下