环境
activiti:5.22.0
springboot:2.5.1
前言
默认已经Springboot2整合activiti5完毕;
可以参考:springboot2整合activiti5.22 maven版
这篇文章是接着上面文章写的。
activiti5.22官方编辑器
网上很多都是使用idea
或者eclipse
插件来画图,这种根据就没法用,代码部署上线后,肯定需要在线编辑器才能满足业务的使用;
期望如下效果:
思路
我们已经把activiti整合完毕;官方其实提供了编辑器,我们可以思考下,编辑器需要哪些东西:
- 前端代码:渲染画图、增删改查的组件显示;
- 后端代码:对应画图页面中的保存、查询等接口;
前端代码
根据网上的资料,我们需要在activiti官方源码中找到如下东西:
前端代码 | 描述 |
---|---|
editor-app | 编辑器 |
modeler.html | 编辑器的入口页面 |
diagram-viewer | 流程跟踪插件,虽然这次用不着,但之后会用到 |
stencilset.json | 本身是英文的,可以通过替换它来达到汉化的效果。 |
favicon.ico | 一个logo |
先下载源码
https://github.com/Activiti/Activiti/tree/activiti-5.22.0
源码路径:activiti-webapp-explorer2
模块中webapp
和resource
中。
放入自己的项目中:
说明:
diagram-viewer
、editor-app
、favicon.ico
、modeler.html
、stencilset.json
移动到自己项目static
文件夹中。model-list.html
不是官方的,是新建流程的入口页面,自己编写的前端代码。
在editor-app/app-cfg.js
中配置一下项目url。这个url是编辑器相关的后台服务的url。
ACTIVITI.CONFIG = {
'contextRoot' : '/service',
};
后端代码
先引入两个activiti的模块,因为编辑器会用到这两个模块。
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-modeler</artifactId>
<version>${activiti.version}</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-diagram-rest</artifactId>
<version>${activiti.version}</version>
</dependency>
其中需要将源码中activiti-modeler
模块的源代码放到src
中,因为需要在其中做部分修改,主要是url
的映射。
其中有3个类,都是Controller:
#获取编辑器组件及配置项信息。
StencilsetRestResource
#根据modelId获取model的节点信息,编辑器根据返回的json进行绘图。
ModelEditorJsonRestResource
#编辑器制图之后,将节点信息以json的形式提交给这个Controller,然后由其进行持久化操作。
ModelSaveRestResource
个人项目存放的位置:
需要修改的地方就三个,在每个Controller类上加上@RequestMapping
注解,并指定值为"service"
(对应前台app-cfg.js
中配置的url)。
···
@RequestMapping("service")
public class StencilsetRestResource {
···
···
@RequestMapping("service")
public class ModelEditorJsonRestResource implements ModelDataJsonConstants {
···
···
@RequestMapping("service")
public class ModelSaveRestResource implements ModelDataJsonConstants {
···
最后别忘了添加包扫描,扫描activiti
提供的这些controller
。
如果启动类和上面放的类都在一个包下,就不用添加。
这样整合部分就基本结束了,此时编辑器已经可以使用了。
至于界面的汉化,界面上各个组件,各个标签上的文字都是在resource
下的stencilset.json
文件中设置的,可以在网上找一个汉化后的stencilset.json
文件替换掉,就能看到中文界面了。
但是入口页面得我们自己写,效果如下:
接下来,我们就要自己去设计这个页面,下面贴出后端代码,前端代码参考文章最后的源码链接;
modeler相关方法的封装
主要需要封装4个方法:
- 新建一个空的模型;
- 所有模型列表;
- 发布模型;
- 删除模型;
(activiti已提供了保存和获取模型节点信息的方法,就是上面的那3个类)
由于这里涉及前后端交互,实现方式随意,主要是activiti的api的调用。
下面只贴出 参考代码,demo,请参考文章最后的链接。
模型管理类
package com.sgy.flowactiviti.admin.controller;
import org.springframework.web.bind.annotation.*;
import java.io.Serializable;
/**
* restful的基本规范
*/
public interface RestServiceController<T, ID extends Serializable> {
/**
* 根据id查询资源
* @param id 资源的唯一标识
* @return
*/
@GetMapping("{id}")
Object getOne(@PathVariable("id") ID id);
/**
* 列表分页
* @param rowSize 一页数据大小
* @param page 当前页码
* @return
*/
@GetMapping
Object getList(@RequestParam(value = "rowSize", defaultValue = "1000", required = false) Integer rowSize
, @RequestParam(value = "page", defaultValue = "1", required = false) Integer page);
/**
* 提交一个资源
* @param entity 资源实体
* @return
*/
@PostMapping
Object postOne(@RequestBody T entity);
/**
* 提交一个资源,并给出标识
* @param id 标识
* @param entity 资源实体
* @return
*/
@PutMapping("{id}")
Object putOne(@PathVariable("id") ID id, @RequestBody T entity);
/**
* 提交一个资源的一部分,不处理null值
* @param id 标识
* @param entity 资源实体
* @return
*/
@PatchMapping("{id}")
Object patchOne(@PathVariable("id") ID id, @RequestBody T entity);
/**
* 根据id删除一个资源
* @param id 标识
* @return
*/
@DeleteMapping("{id}")
Object deleteOne(@PathVariable("id") ID id);
}
具体实现:
package com.sgy.flowactiviti.admin.controller;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.sgy.flowactiviti.admin.util.Status;
import com.sgy.flowactiviti.admin.util.ToWeb;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.Model;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.io.UnsupportedEncodingException;
import java.util.List;
/**
* 模型管理
*/
@RestController
@RequestMapping("models")
public class ModelerController implements RestServiceController<Model, String> {
@Autowired
RepositoryService repositoryService;
@Autowired
ObjectMapper objectMapper;
/**
* 新建一个空模型
* @return
* @throws UnsupportedEncodingException
*/
@PostMapping("newModel")
public Object newModel() throws UnsupportedEncodingException {
//初始化一个空模型
Model model = repositoryService.newModel();
//设置一些默认信息
String name = "new-process";
String description = "";
int revision = 1;
String key = "process";
ObjectNode modelNode = objectMapper.createObjectNode();
modelNode.put(ModelDataJsonConstants.MODEL_NAME, name);
modelNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
modelNode.put(ModelDataJsonConstants.MODEL_REVISION, revision);
model.setName(name);
model.setKey(key);
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.put("stencilset", stencilSetNode);
repositoryService.addModelEditorSource(id,editorNode.toString().getBytes("utf-8"));
return ToWeb.buildResult().redirectUrl("/editor?modelId="+id);
}
/**
* 发布模型为流程定义
* @param id
* @return
* @throws Exception
*/
@PostMapping("{id}/deployment")
public Object deploy(@PathVariable("id")String id) throws Exception {
//获取模型
Model modelData = repositoryService.getModel(id);
byte[] bytes = repositoryService.getModelEditorSource(modelData.getId());
if (bytes == null) {
return ToWeb.buildResult().status(Status.FAIL)
.msg("模型数据为空,请先设计流程并成功保存,再进行发布。");
}
JsonNode modelNode = new ObjectMapper().readTree(bytes);
BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode);
if(model.getProcesses().size()==0){
return ToWeb.buildResult().status(Status.FAIL)
.msg("数据模型不符要求,请至少设计一条主线流程。");
}
byte[] 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();
modelData.setDeploymentId(deployment.getId());
repositoryService.saveModel(modelData);
return ToWeb.buildResult().refresh();
}
@Override
public Object getOne(@PathVariable("id") String id) {
Model model = repositoryService.createModelQuery().modelId(id).singleResult();
return ToWeb.buildResult().setObjData(model);
}
@Override
public Object getList(@RequestParam(value = "rowSize", defaultValue = "1000", required = false) Integer rowSize, @RequestParam(value = "page", defaultValue = "1", required = false) Integer page) {
List<Model> list = repositoryService.createModelQuery().listPage(rowSize * (page - 1)
, rowSize);
long count = repositoryService.createModelQuery().count();
return ToWeb.buildResult().setRows(
ToWeb.Rows.buildRows().setCurrent(page)
.setTotalPages((int) (count/rowSize+1))
.setTotalRows(count)
.setList(list)
.setRowSize(rowSize)
);
}
public Object deleteOne(@PathVariable("id")String id){
repositoryService.deleteModel(id);
return ToWeb.buildResult().refresh();
}
@Override
public Object postOne(@RequestBody Model entity) {
throw new UnsupportedOperationException();
}
@Override
public Object putOne(@PathVariable("id") String s, @RequestBody Model entity) {
throw new UnsupportedOperationException();
}
@Override
public Object patchOne(@PathVariable("id") String s, @RequestBody Model entity) {
throw new UnsupportedOperationException();
}
}
前端代码,你们就参考链接吧:
https://github.com/b3601993/spring-boot-with-activiti
总结
总体最麻烦的可能就是模型列表前端页面相关的代码,因为这块得我们自己去实现设计。
对于我后端人员来说,真的太难受了。
参考地址: