> 前面我們介紹了流程模型的創建、以及用流程模型部署流程定義。本節我們主要介紹,流程定義文件—流程定義—流程模型的相互轉化
[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`表中呢?
思路如下:

## 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包

然后我們查看`act_re_deployment`和`act_re_procdef`表,可以看到已經生成了對應的流程定義記錄(我執行了2次,所以2條記錄)


然后我們就可以根據流程定義來發起流程了,本節我們主要介紹內容不是這個,我們先來介紹流程定義如何轉化流程模型。
### 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)

轉化成功后,我們查看`act_re_model`表,可以看到已經生成了對應的流程模型。

### 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();
}
}
}
~~~


我們查看對應的數據庫表:


可以看到生成了對應的流程定義,因為我們使用的是同一個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);
}
}
~~~
測試結果:

如果直接使用瀏覽器訪問,則會下載bpmn文件

### 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("部署成功");
}
~~~

### 2.6 、查看流程模型
訪問[http://localhost:8080/modeler/modeler.html?modelId=1](http://localhost:8080/modeler/modeler.html?modelId=1)
就可以愉快的在上面設計流程圖了。

流程定義文件—流程定義—流程模型 之間的相互轉化,就介紹到這里。
### 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("刪除流程實例成功");
}
~~~

### 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("刪除流程模型成功");
}
```

## 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);
}
```

- 使用教程
- 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生成靜態接口文檔