一、背景
最近因为项目需求,我们需要引入一个流程引擎框架。基于以前做办公系统的经验,我就选择了activiti流程引擎框架,不过版本已经更新了好几个了,前后端技术也更新了,集成方式也不同了。于是我开始找资料,发现大多都是后端集成的方式,包括前端代码也喜欢放在后端resources中,这是五年前的模式了,我感觉这种方式很不友好。况且,目前都采用前后端分离的架构模式,前端框架也比较成熟和流行,我们还把html放在后端,从前端、后端、部署三个层面都显的很鸡肋,后面零零散散找了一些资料,踩了很多坑,奋斗了几个晚上,终于集成完毕。因此写个博客记录一下,有描述不详细的地方,欢迎留言,我目前还留存一点记忆,可以解答,哈哈。
二、前端集成
2.1、搭建一个vue项目,将activiti6的前端代码放在public目录下(前端代码可以去官网下载)
2.2、集成模型设计。新建一个vue文件,内容如下
<template>
<div style="position:relative;height: 100%;">
<iframe
id="iframe"
:src="modelerUrl"
frameborder="0"
width="100%"
height="720px"
scrolling="auto"
/>
<Spin v-if="modelerLoading" fix size="large" />
</div>
</template>
<script>
import { getToken } from '@/utils/auth'
export default {
name: 'ModelDefine',
components: {},
data() {
return {
modelerLoading: true,
modelerUrl: '/static/modeler.html?modelId=' + this.$route.query.id + '&time=' + new Date().getTime()
}
},
computed: {
token() {
return 'Bearer ' + getToken()
}
},
created() {},
mounted() {
window.getMyVue = this
},
methods: {}
}
</script>
<style lang="scss" scoped>
.iframe {
width: 100%;
height: calc(100vh - 154px);
}
</style>
结果如图:
2.3、对接token。每个系统都有自己的登录逻辑,activiti前端支持对接项目的token。打开static/modeler.html添加一下代码
<script>
(
function (open) {
XMLHttpRequest.prototype.open = function (method, url, async, user, pass) {
open.call(this, method, url, async, user, pass); //this指XMLHttpRequest
this.setRequestHeader("Authorization", window.parent.getMyVue.token); //mounted时传入的token
};
}
)(XMLHttpRequest.prototype.open);
</script>
2.4、修改接口配置。activiti默认的api配置也是支持修改的,可以对应我们自己的业务系统的路由。打开static/editor-app/app-cfg.js
完成上述几步,前端集成基本告一段落了,需要我们重写后台接口,完成模型的新建和保存。
三、后端集成
3.1、搭建一个springboot工程,本次使用的springboot2.4.2
3.2、集成jar包
<!-- activiti 流程引擎 -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-engine</artifactId>
<version>6.0.0</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-json-converter</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter-basic</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-codec</artifactId>
<version>1.7</version>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-css</artifactId>
<version>1.7</version>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-svg-dom</artifactId>
<version>1.7</version>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-svggen</artifactId>
<version>1.7</version>
</dependency>
3.3、配置activiti(springboot项目的yml文件)
spring:
activiti:
check-process-definitions: false # 自动检查、部署流程定义文件
database-schema-update: true # 自动更新数据库结构
process-definition-location-prefix: classpath:/processes # 流程定义文件存储目录
3.4、重写新增模型接口
/**
* 新增模型
* */
@PostMapping("/add")
public AjaxResult addModel(@RequestBody ModelEntityForAdd newModel){
ObjectNode modelNode = objectMapper.createObjectNode();
modelNode.put(MODEL_NAME, newModel.getName());
modelNode.put(MODEL_DESCRIPTION, newModel.getDescription());
modelNode.put(MODEL_REVISION, "1");
Model model = repositoryService.newModel();
model.setName(newModel.getName());
model.setKey(newModel.getKey());
model.setCategory(newModel.getCategory());
model.setMetaInfo(modelNode.toString());
repositoryService.saveModel(model);
String id = model.getId();
//完善ModelEditorSource
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.putPOJO("stencilset", stencilSetNode);
repositoryService.addModelEditorSource(id, editorNode.toString().getBytes(StandardCharsets.UTF_8));
return AjaxResult.success(id);
}
3.5、重写获取流程定义接口
/**
* 获取流程定义json数据
*
* @param modelId
* @return
*/
@GetMapping(value = "/{modelId}/json")
public ObjectNode getEditorJson(@PathVariable String modelId) {
ObjectNode modelNode = null;
Model model = repositoryService.getModel(modelId);
if (model != null) {
try {
if (StringUtils.isNotEmpty(model.getMetaInfo())) {
modelNode = (ObjectNode) objectMapper.readTree(model.getMetaInfo());
} else {
modelNode = objectMapper.createObjectNode();
modelNode.put(MODEL_NAME, model.getName());
}
modelNode.put(MODEL_ID, model.getId());
byte[] modelEditorSource = repositoryService.getModelEditorSource(model.getId());
ObjectNode editorJsonNode = (ObjectNode) objectMapper.readTree(new String(modelEditorSource, StandardCharsets.UTF_8));
modelNode.putPOJO("model", editorJsonNode);
} catch (Exception e) {
throw new ActivitiException("Error creating model JSON", e);
}
}
return modelNode;
}
3.6、重写保存模型接口
/**
* 保存流程定义数据
*/
@PutMapping(value = "/{modelId}/save")
public void saveModel(@PathVariable String modelId, @RequestParam("name") String name, @RequestParam("json_xml") String json_xml,
@RequestParam("svg_xml") String svg_xml, @RequestParam("description") String description) {
try {
Model model = repositoryService.getModel(modelId);
ObjectNode modelJson = (ObjectNode) objectMapper.readTree(model.getMetaInfo());
modelJson.put(MODEL_NAME, name);
modelJson.put(MODEL_DESCRIPTION, description);
model.setMetaInfo(modelJson.toString());
model.setName(name);
model.setVersion(model.getVersion() + 1);
repositoryService.saveModel(model);
repositoryService.addModelEditorSource(model.getId(), Objects.requireNonNull(json_xml.getBytes(StandardCharsets.UTF_8)));
InputStream svgStream = new ByteArrayInputStream(Objects.requireNonNull(svg_xml.getBytes(StandardCharsets.UTF_8)));
TranscoderInput input = new TranscoderInput(svgStream);
PNGTranscoder transcoder = new PNGTranscoder();
// Setup output
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
TranscoderOutput output = new TranscoderOutput(outStream);
// Do the transformation
transcoder.transcode(input, output);
final byte[] result = outStream.toByteArray();
repositoryService.addModelEditorSourceExtra(model.getId(), result);
outStream.close();
} catch (Exception e) {
throw new ActivitiException("Error saving model", e);
}
}
完成上述三个接口,再结合前端,就基本完成了模型的创建、编辑、保存了。至于后续的模型部署、流程实例等等,网上都有很多资料了。