> 前面我们介绍了流程模型的创建、以及用流程模型部署流程定义。本节我们主要介绍,流程定义文件—流程定义—流程模型的相互转化
[TOC]
*****
## 1、回顾
### 1.1 、生成流程模型
此处贴出生成流程模型的代码,流程模型是啥?说白了就是创建一个流程模型,然后我们可以在上面画流程图。
~~~
/**
* 创建模型
*/
@Test
public void create() {
String name = "请假流程";
String key = "leave";
String description = "这是一个简单的请假流程";
try {
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode editorNode = objectMapper.createObjectNode();
editorNode.put("id", "canvas");
editorNode.put("resourceId", "canvas");
ObjectNode stencilSetNode = objectMapper.createObjectNode();
stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
editorNode.put("stencilset", stencilSetNode);
ObjectNode modelObjectNode = objectMapper.createObjectNode();
modelObjectNode.put(MODEL_NAME, name);
modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, 1);
description = StringUtils.defaultString(description);
modelObjectNode.put(MODEL_DESCRIPTION, description);
Model newModel = repositoryService.newModel();
newModel.setMetaInfo(modelObjectNode.toString());
newModel.setName(name);
newModel.setKey(StringUtils.defaultString(key));
repositoryService.saveModel(newModel);
repositoryService.addModelEditorSource(newModel.getId(), editorNode.toString().getBytes("utf-8"));
System.out.println("生成的moduleId:"+newModel.getId());
} catch (Exception e) {
}
}
~~~
### 1.2 、部署流程定义
这个在前面的章节已经介绍过了,把画好的流程图部署好才能发起流程。
~~~
/**
* 部署流程定义
*/
@Test
public void deployTest(){
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("leave.bpmn")
.deploy();
assertNotNull(deployment);
}
~~~
### 1.3 、 引出问题
**第一个问题**:步骤 1.2 中,部署流程定义是直接使用`leave.bpmn`文件部署的。但是我们使用Activiti Modeler设计流程图后,流程模型数据是直接保存在数据库表中的(act\_re\_model)。并不是直接生成一个`.bpmn`文件。这时如何部署流程定义呢?
* 方式一:使用Activiti Modeler 设计器保存在act\_re\_model表中的模型记录,导出一个`.bpmn`文件后再部署(不方便)
* 方式二:直接使用 act\_re\_model 表中的模型记录来部署流程定义。(正确用法)
**第二个问题**:就是如果我们现在只有一个别人提供的`.bpmn`文件,如何才能将它保存到`act_re_model`表中呢?
思路如下:
![](https://img.kancloud.cn/d1/dc/d1dc57b6883451791051b412d11a5881_977x455.png)
## 2、流程定义文件—流程定义—流程模型 相互转化
### 2.1 、 bpmn文件转流程定义(act\_re\_procdef)
> 上面的1.2 中,我们是直接将文件放在了`src/main/resources`目录下。下面我们改造一下。改为上传附件部署流程定义。
这里改动较大、把常用工具类类单独放在了workflow-common这个module中。贴出关键代码:
新建 ProcessDefinitionController.java
~~~
package com.sxdx.workflow.activiti.rest.controller;
import com.sxdx.common.config.GlobalConfig;
import com.sxdx.common.constant.CodeEnum;
import com.sxdx.common.constant.Constants;
import com.sxdx.common.util.CommonResponse;
import com.sxdx.common.util.StringUtils;
import com.sxdx.common.util.file.FileUploadUtils;
import com.sxdx.workflow.activiti.rest.service.ProcessDefinitionService;
import org.activiti.engine.RepositoryService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@Controller
@RequestMapping("/definition")
public class ProcessDefinitionController {
private static final Logger log = LoggerFactory.getLogger(ProcessDefinitionController.class);
private String prefix = "definition";
@Autowired
private ProcessDefinitionService processDefinitionService;
@Autowired
private RepositoryService repositoryService;
/**
* 部署流程定义
*/
@PostMapping("/upload")
@ResponseBody
public CommonResponse upload(@RequestParam("processDefinition") MultipartFile file) {
try {
if (!file.isEmpty()) {
String extensionName = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf('.') + 1);
if (!"bpmn".equalsIgnoreCase(extensionName)
&& !"zip".equalsIgnoreCase(extensionName)
&& !"bar".equalsIgnoreCase(extensionName)) {
return new CommonResponse().code(CodeEnum.FAILURE.getCode()).message("流程定义文件仅支持 bpmn, zip 和 bar 格式!");
}
// p.s. 此时 FileUploadUtils.upload() 返回字符串 fileName 前缀为 Constants.RESOURCE_PREFIX,需剔除
// 详见: FileUploadUtils.getPathFileName(...)
String fileName = FileUploadUtils.upload(GlobalConfig.getProfile() + "/processDefiniton", file);
if (StringUtils.isNotBlank(fileName)) {
String realFilePath = GlobalConfig.getProfile() + fileName.substring(Constants.RESOURCE_PREFIX.length());
processDefinitionService.deployProcessDefinition(realFilePath);
return new CommonResponse().code(CodeEnum.SUCCESS.getCode()).message("部署成功");
}
}
return new CommonResponse().code(CodeEnum.FAILURE.getCode()).message("不允许上传空文件!");
}
catch (Exception e) {
log.error("上传流程定义文件失败!", e);
return new CommonResponse().code(CodeEnum.FAILURE.getCode().toString()).message(e.getMessage());
}
}
}
~~~
我们使用postman做测试:
支持单个bpmn文件上传,也支持多个文件压缩成zip包、或者bar包
![](https://img.kancloud.cn/7b/0c/7b0cfda9325649f6826fc0c684f16542_1538x602.png)
然后我们查看`act_re_deployment`和`act_re_procdef`表,可以看到已经生成了对应的流程定义记录(我执行了2次,所以2条记录)
![](https://img.kancloud.cn/db/a7/dba72969ff583afeb9e32e46e799a366_882x180.png)
![](https://img.kancloud.cn/da/79/da79c0c1c301a1dda0850d6f9625d14b_1566x125.png)
然后我们就可以根据流程定义来发起流程了,本节我们主要介绍内容不是这个,我们先来介绍流程定义如何转化流程模型。
### 2.2、流程定义(act\_re\_procdef)转流程模型(act\_re\_model)
执行代码如下:
~~~
/**
* 转换流程定义为模型
* @param processDefinitionId 流程定义id
* @return
* @throws UnsupportedEncodingException
* @throws XMLStreamException
*/
@PostMapping(value = "/convertToModel")
@ResponseBody
public CommonResponse convertToModel(@Param("processDefinitionId") String processDefinitionId)
throws UnsupportedEncodingException, XMLStreamException {
org.activiti.engine.repository.ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionId(processDefinitionId).singleResult();
InputStream bpmnStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(),
processDefinition.getResourceName());
XMLInputFactory xif = XMLInputFactory.newInstance();
InputStreamReader in = new InputStreamReader(bpmnStream, "UTF-8");
XMLStreamReader xtr = xif.createXMLStreamReader(in);
BpmnModel bpmnModel = new BpmnXMLConverter().convertToBpmnModel(xtr);
BpmnJsonConverter converter = new BpmnJsonConverter();
ObjectNode modelNode = converter.convertToJson(bpmnModel);
Model modelData = repositoryService.newModel();
modelData.setKey(processDefinition.getKey());
modelData.setName(processDefinition.getResourceName());
modelData.setCategory(processDefinition.getDeploymentId());
ObjectNode modelObjectNode = new ObjectMapper().createObjectNode();
modelObjectNode.put(ModelDataJsonConstants.MODEL_NAME, processDefinition.getName());
modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, 1);
modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, processDefinition.getDescription());
modelData.setMetaInfo(modelObjectNode.toString());
repositoryService.saveModel(modelData);
repositoryService.addModelEditorSource(modelData.getId(), modelNode.toString().getBytes("utf-8"));
return new CommonResponse().code(CodeEnum.SUCCESS.getCode()).message("转换成功");
}
~~~
postman测试结果(参数是流程定义id,即`act_re_procdef`表的主键id)
![](https://img.kancloud.cn/19/93/19939010b410805a21fe6661656cf46d_1523x590.png)
转化成功后,我们查看`act_re_model`表,可以看到已经生成了对应的流程模型。
![](https://img.kancloud.cn/ce/f4/cef42e51333c905b0b57b07c9d8ecf0c_1570x146.png)
### 2.3、流程模型(act\_re\_model)转流程定义(act\_re\_procdef)
新建ModelerController.java:
~~~
**
* 根据Model部署流程,参数为act_re_model表的ID_字段
*/
@RequestMapping(value = "/modeler/deploy/{modelId}")
@ResponseBody
public AjaxResult deploy(@PathVariable("modelId") String modelId, RedirectAttributes redirectAttributes) {
try {
Model modelData = repositoryService.getModel(modelId);
ObjectNode modelNode = (ObjectNode) new ObjectMapper().readTree(repositoryService.getModelEditorSource(modelData.getId()));
byte[] bpmnBytes = null;
BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode);
bpmnBytes = new BpmnXMLConverter().convertToXML(model);
String processName = modelData.getName() + ".bpmn20.xml";
Deployment deployment = repositoryService.createDeployment().name(modelData.getName()).addString(processName, new String(bpmnBytes, "UTF-8")).deploy();
LOGGER.info("部署成功,部署ID=" + deployment.getId());
return success("部署成功");
} catch (Exception e) {
LOGGER.error("根据模型部署流程失败:modelId={}", modelId, e);
}
return error("部署失败");
}
/**
* 导出model的xml文件
*/
@RequestMapping(value = "/modeler/export/{modelId}")
public void export(@PathVariable("modelId") String modelId, HttpServletResponse response) {
try {
Model modelData = repositoryService.getModel(modelId);
BpmnJsonConverter jsonConverter = new BpmnJsonConverter();
JsonNode editorNode = new ObjectMapper().readTree(repositoryService.getModelEditorSource(modelData.getId()));
BpmnModel bpmnModel = jsonConverter.convertToBpmnModel(editorNode);
// 流程非空判断
if (!CollectionUtils.isEmpty(bpmnModel.getProcesses())) {
BpmnXMLConverter xmlConverter = new BpmnXMLConverter();
byte[] bpmnBytes = xmlConverter.convertToXML(bpmnModel);
ByteArrayInputStream in = new ByteArrayInputStream(bpmnBytes);
String filename = bpmnModel.getMainProcess().getId() + ".bpmn";
response.setHeader("Content-Disposition", "attachment; filename=" + filename);
IOUtils.copy(in, response.getOutputStream());
response.flushBuffer();
} else {
try {
response.sendRedirect("/modeler/modelList");
} catch (IOException ex) {
ex.printStackTrace();
}
}
} catch (Exception e) {
LOGGER.error("导出model的xml文件失败:modelId={}", modelId, e);
try {
response.sendRedirect("/modeler/modelList");
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
~~~
![](https://img.kancloud.cn/25/16/25160b405c032b3203d7f3d989695099_1520x582.png)
![](https://img.kancloud.cn/59/a6/59a65fc7c76b2dc8f09dab0ef039ef8a_1482x102.png)
我们查看对应的数据库表:
![](https://img.kancloud.cn/c0/ca/c0ca6c45739e530f1977d38e7bee5d39_1171x268.png)
![](https://img.kancloud.cn/22/01/220122f2c8bd4fb32ffc5b683bf5eee1_1663x299.png)
可以看到生成了对应的流程定义,因为我们使用的是同一个KEY的流程,所以生成的记录的VERSION\_字段(版本)自动递增变为了3。有一点需要说明。当我们直接使用流程定义KEY(这里是leaveCounterSign)发起流程时,系统会默认选择吧版本更高的流程定义。
### 2.4、将流程模型导出为bpmn文件
~~~
/**
* 将流程模型导出为 bpmn文件
*/
@RequestMapping(value = "/modeler/export/{modelId}")
public void export(@PathVariable("modelId") String modelId, HttpServletResponse response) {
try {
Model modelData = repositoryService.getModel(modelId);
BpmnJsonConverter jsonConverter = new BpmnJsonConverter();
JsonNode editorNode = new ObjectMapper().readTree(repositoryService.getModelEditorSource(modelData.getId()));
BpmnModel bpmnModel = jsonConverter.convertToBpmnModel(editorNode);
// 流程非空判断
if (!CollectionUtils.isEmpty(bpmnModel.getProcesses())) {
BpmnXMLConverter xmlConverter = new BpmnXMLConverter();
byte[] bpmnBytes = xmlConverter.convertToXML(bpmnModel);
ByteArrayInputStream in = new ByteArrayInputStream(bpmnBytes);
String filename = bpmnModel.getMainProcess().getId() + ".bpmn";
response.setHeader("Content-Disposition", "attachment; filename=" + filename);
IOUtils.copy(in, response.getOutputStream());
response.flushBuffer();
} else {
logger.error("导出model的xml文件失败:modelId={}", modelId);
}
} catch (Exception e) {
logger.error("导出model的xml文件失败:modelId={}", modelId, e);
}
}
~~~
测试结果:
![](https://img.kancloud.cn/2e/86/2e8611ab844fc62a1a395972b4b36984_1529x876.png)
如果直接使用浏览器访问,则会下载bpmn文件
![](https://img.kancloud.cn/74/79/7479b489c6338de5fdf1c6e1db135d34_1397x648.png)
### 2.5、创建流程模型
这个我们在第3节已经介绍过,不做其他介绍。直接贴代码:
~~~
/**
* 创建模型
*/
@RequestMapping(value = "/modeler/create")
@ResponseBody
public CommonResponse create(@RequestParam(value = "name",required=true) String name,
@RequestParam(value = "key",required=true) String key,
@RequestParam(value = "description",required=true) String description) {
/* String name = "请假流程";
String key = "qingjia";
String description = "这是一个简单的请假流程";*/
try {
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode editorNode = objectMapper.createObjectNode();
editorNode.put("id", "canvas");
editorNode.put("resourceId", "canvas");
ObjectNode stencilSetNode = objectMapper.createObjectNode();
stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
editorNode.put("stencilset", stencilSetNode);
ObjectNode modelObjectNode = objectMapper.createObjectNode();
modelObjectNode.put(MODEL_NAME, name);
modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, 1);
description = StringUtils.defaultString(description);
modelObjectNode.put(MODEL_DESCRIPTION, description);
Model newModel = repositoryService.newModel();
newModel.setMetaInfo(modelObjectNode.toString());
newModel.setName(name);
newModel.setKey(StringUtils.defaultString(key));
repositoryService.saveModel(newModel);
repositoryService.addModelEditorSource(newModel.getId(), editorNode.toString().getBytes("utf-8"));
System.out.println("生成的moduleId:"+newModel.getId());
return new CommonResponse().code(CodeEnum.SUCCESS.getCode()).message("部署成功").data(newModel);
} catch (Exception e) {
logger.error("创建模型失败");
}
return new CommonResponse().code(CodeEnum.SUCCESS.getCode()).message("部署成功");
}
~~~
![](https://img.kancloud.cn/a5/bd/a5bd4220c1b3cccbbf42eafbb090b208_1531x879.png)
### 2.6 、查看流程模型
访问[http://localhost:8080/modeler/modeler.html?modelId=1](http://localhost:8080/modeler/modeler.html?modelId=1)
就可以愉快的在上面设计流程图了。
![](https://img.kancloud.cn/c1/eb/c1eb72b8a7ea472cc1997d745a170b39_2544x1246.png)
流程定义文件—流程定义—流程模型 之间的相互转化,就介绍到这里。
### 2.7、删除已部署的流程定义
~~~
/**
* 删除已部署的流程定义
* @param deploymentId 流程部署ID
*/
@GetMapping(value = "/process/delete/{deploymentId}")
@ApiOperation(value = "删除已部署的流程定义",notes = "删除已部署的流程定义")
public CommonResponse delete(@PathVariable("deploymentId") @ApiParam("流程定义ID (act_re_deployment表id)") String deploymentId) {
List<ProcessInstance> instanceList = runtimeService.createProcessInstanceQuery()
.deploymentId(deploymentId)
.list();
if (!CollectionUtils.isEmpty(instanceList)) {
// 存在流程实例的流程定义
throw new CommonException("删除失败,存在运行中的流程实例");
}
repositoryService.deleteDeployment(deploymentId, true); // true 表示级联删除引用,比如 act_ru_execution 数据
return new CommonResponse().code(CodeEnum.SUCCESS.getCode()).message("删除流程实例成功");
}
~~~
![](https://img.kancloud.cn/00/76/00762bf3cffed8e9a70f2f4ae7fa0e86_1775x1155.png)
### 2.8、删除流程模型
```
@GetMapping(value = "/modeler/delete/{modelId}")
@ApiOperation(value = "删除流程模型",notes = "删除流程模型")
public CommonResponse remove(@PathVariable("modelId") @ApiParam("流程模型Id") String modelId) {
repositoryService.deleteModel(modelId);
return new CommonResponse().code(CodeEnum.SUCCESS.getCode()).message("删除流程模型成功");
}
```
![](https://img.kancloud.cn/34/d7/34d79f18034450c29405f9a25c41e378_1823x1156.png)
## 3、内容补充
### 3.1 获取流程模型列表
此处添加一个获取流程模型列表的接口,其中用到了**自定义分页插件**。自定义分页插件在后面的**历史数据模块、补充获取流程定义列表接口**中会有介绍。
```
@GetMapping(value = "/modelList")
@ApiOperation(value = "获取流程模型列表",notes = "获取流程模型列表")
public CommonResponse getModelerList(@RequestParam(value = "pageNum", required = false,defaultValue = "1")
@ApiParam(value = "页码" ,required = false)int pageNum,
@RequestParam(value = "pageSize", required = false,defaultValue = "10")
@ApiParam(value = "条数" ,required = false)int pageSize){
ModelQuery modelQuery = repositoryService.createModelQuery();
Page page = new Page(pageNum,pageSize);
List<Model> list = modelQuery.orderByCreateTime().desc()
.listPage(page.getFirstResult(),page.getMaxResults());
int total = (int) modelQuery.count();
page.setTotal(total);
page.setList(list);
return new CommonResponse().code(CodeEnum.SUCCESS.getCode()).message("获取流程模型列表成功").data(page);
}
```
![](https://img.kancloud.cn/f1/ab/f1ab9f97e18c2d3878aa1ae47a9a626b_1282x933.png)
- 使用教程
- 1、环境说明
- 2、导入教程
- 3、系统展示
- 4、更新历史
- 搭建教程
- 第一章:Activiti模块
- 1、基本概念
- 2、资料下载
- 3、环境搭建
- 4、集成Activiti-Modeler流程设计器
- 5、七大Service接口
- 6、流程定义文件—流程定义—流程模型 的相互转化
- 7、用户和用户组
- 8、任务表单
- 8.1、表单分类
- 8.2 、动态表单实战、集成Swagger、Logback
- 8.3、外置表单实战
- 8.4、普通表单实战,集成Thymeleaf,Mybatis-Plus
- 8.5、表单模式选型
- 9、多实例(会签)
- 10、子流程和调用活动
- 10.1、子流程
- 10.2、事件子流程
- 10.3、调用活动
- 10.4、事务子流程
- 11、流程历史管理、补充获取流程定义列表接口
- 12、Activiti事件
- 12.1、 事件类别
- 12.2、 Activiti启动事件
- 12.3、Activiti结束事件
- 12.4、边界事件(一)
- 12.5、边界事件(二)
- 12.6、中间事件
- 13、网关
- 14、Activiti审批意见管理
- 15、Activiti流程驳回、流程回退
- 16、Activiti任务委托
- 17、Activiti流程的挂起、激活
- 第二章:基础架构完善
- 1、Spring-Security-OAuth2简介
- 2、搭建认证服务器
- 3、搭建资源服务器
- 4、Activiti自带的Rest接口
- 5、添加JWT支持
- 6、数据库存储授权码Code,Client信息
- 第三章、集成RBAC权限管理
- 1、RBAC-基于角色的访问控制
- 2、替换Activiti用户和用户组
- 3、Spring-Security获取当前操作人信息
- 4、OauthUserDetailService改造
- 第四章、使用Swagger生成静态接口文档