Activiti 5.10版本把原本独立的Activiti Modeler模块整合到了Activiti Explorer中,两者相结合使用起来很方便,通过Modeler设计的流程模型可以直接部署到引擎,也可以把已经部署的流程转换为Model从而在Modeler中编辑。
在实际应用中也有这样的需求,把Modeler整合到业务系统中可以供管理员使用,或者作为BPM平台的一部分存在,很遗憾官方没有给出如何整合Modeler的文档。
首先需要从Github下载源码:https://github.com/Activiti/Activiti;可以直接用Git克隆,也可以下载zip格式的压缩包。
复制的所有文件均在activiti-webapp-explorer2目录中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<
dependency
>
<
groupid
>org.activiti</
groupid
>
<
artifactid
>activiti-explorer</
artifactid
>
<
version
>5.14</
version
>
<
exclusions
>
<
exclusion
>
<
artifactid
>vaadin</
artifactid
>
<
groupid
>com.vaadin</
groupid
>
</
exclusion
>
<
exclusion
>
<
artifactid
>dcharts-widget</
artifactid
>
<
groupid
>org.vaadin.addons</
groupid
>
</
exclusion
>
<
exclusion
>
<
artifactid
>activiti-simple-workflow</
artifactid
>
<
groupid
>org.activiti</
groupid
>
</
exclusion
>
</
exclusions
>
</
dependency
>
<
dependency
>
<
groupid
>org.activiti</
groupid
>
<
artifactid
>activiti-modeler</
artifactid
>
<
version
>5.14</
version
>
</
dependency
>
|
添加一个ExplorerRestApplication.java类保存到项目中,注册了一些REST路由。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
package
org.activiti.explorer.rest.application;
import
org.activiti.editor.rest.application.ModelerServicesInit;
import
org.activiti.rest.api.DefaultResource;
import
org.activiti.rest.application.ActivitiRestApplication;
import
org.activiti.rest.filter.JsonpFilter;
import
org.restlet.Restlet;
import
org.restlet.routing.Router;
public
class
ExplorerRestApplication
extends
ActivitiRestApplication {
public
ExplorerRestApplication() {
super
();
}
/**
* Creates a root Restlet that will receive all incoming calls.
*/
@Override
public
synchronized
Restlet createInboundRoot() {
Router router =
new
Router(getContext());
router.attachDefault(DefaultResource.
class
);
ModelerServicesInit.attachResources(router);
DiagramServicesInit.attachResources(router);
JsonpFilter jsonpFilter =
new
JsonpFilter(getContext());
jsonpFilter.setNext(router);
return
jsonpFilter;
}
}
|
在web.xml文件中添加如下配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<!-- Restlet adapter, used to expose modeler functionality through REST -->
<
servlet
>
<
servlet-name
>RestletServlet</
servlet-name
>
<
servlet-class
>org.restlet.ext.servlet.ServerServlet</
servlet-class
>
<
init-param
>
<!-- Application class name -->
<
param-name
>org.restlet.application</
param-name
>
<
param-value
>org.activiti.explorer.rest.application.ExplorerRestApplication</
param-value
>
</
init-param
>
</
servlet
>
<!-- Catch all service requests -->
<
servlet-mapping
>
<
servlet-name
>RestletServlet</
servlet-name
>
<
url-pattern
>/service/*</
url-pattern
>
</
servlet-mapping
>
|
使用Spring MVC做了一个简单的封装,也可以使用其他的MVC实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
package
me.kafeitu.demo.activiti.web.workflow;
import
java.io.ByteArrayInputStream;
import
java.util.List;
import
javax.servlet.http.HttpServletRequest;
import
javax.servlet.http.HttpServletResponse;
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.apache.commons.io.IOUtils;
import
org.apache.commons.lang3.StringUtils;
import
org.codehaus.jackson.JsonNode;
import
org.codehaus.jackson.map.ObjectMapper;
import
org.codehaus.jackson.node.ObjectNode;
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.PathVariable;
import
org.springframework.web.bind.annotation.RequestMapping;
import
org.springframework.web.bind.annotation.RequestParam;
import
org.springframework.web.servlet.ModelAndView;
import
org.springframework.web.servlet.mvc.support.RedirectAttributes;
/**
* 流程模型控制器
*
* @author henryyan
*/
@Controller
@RequestMapping
(value =
"/workflow/model"
)
public
class
ModelController {
protected
Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
RepositoryService repositoryService;
/**
* 模型列表
*/
@RequestMapping
(value =
"list"
)
public
ModelAndView modelList() {
ModelAndView mav =
new
ModelAndView(
"workflow/model-list"
);
List<model> list = repositoryService.createModelQuery().list();
mav.addObject(
"list"
, list);
return
mav;
}
/**
* 创建模型
*/
@RequestMapping
(value =
"create"
)
public
void
create(
@RequestParam
(
"name"
) String name,
@RequestParam
(
"key"
) String key,
@RequestParam
(
"description"
) String description,
HttpServletRequest request, HttpServletResponse response) {
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);
Model modelData = repositoryService.newModel();
ObjectNode modelObjectNode = objectMapper.createObjectNode();
modelObjectNode.put(ModelDataJsonConstants.MODEL_NAME, name);
modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION,
1
);
description = StringUtils.defaultString(description);
modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
modelData.setMetaInfo(modelObjectNode.toString());
modelData.setName(name);
modelData.setKey(StringUtils.defaultString(key));
repositoryService.saveModel(modelData);
repositoryService.addModelEditorSource(modelData.getId(), editorNode.toString().getBytes(
"utf-8"
));
response.sendRedirect(request.getContextPath() +
"/service/editor?id="
+ modelData.getId());
}
catch
(Exception e) {
logger.error(
"创建模型失败:"
, e);
}
}
/**
* 根据Model部署流程
*/
@RequestMapping
(value =
"deploy/{modelId}"
)
public
String 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)).deploy();
redirectAttributes.addFlashAttribute(
"message"
,
"部署成功,部署ID="
+ deployment.getId());
}
catch
(Exception e) {
logger.error(
"根据模型部署流程失败:modelId={}"
, modelId, e);
}
return
"redirect:/workflow/model/list"
;
}
/**
* 导出model的xml文件
*/
@RequestMapping
(value =
"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);
BpmnXMLConverter xmlConverter =
new
BpmnXMLConverter();
byte
[] bpmnBytes = xmlConverter.convertToXML(bpmnModel);
ByteArrayInputStream in =
new
ByteArrayInputStream(bpmnBytes);
IOUtils.copy(in, response.getOutputStream());
String filename = bpmnModel.getMainProcess().getId() +
".bpmn20.xml"
;
response.setHeader(
"Content-Disposition"
,
"attachment; filename="
+ filename);
response.flushBuffer();
}
catch
(Exception e) {
logger.error(
"导出model的xml文件失败:modelId={}"
, modelId, e);
}
}
}
</model>
|
如果使用Spring代理引擎,并且在项目中同时有activiti.cfg.xml文件(不管在main/resources还是test/resources目录),在activiti.cfg.xml里面的引擎中添加下面的配置参数,否则会导致打开Modeler的编辑页面时读取数据返回204状态码。
1
|
<
property
name
=
"processEngineName"
value
=
"test"
></
property
>
|
引擎默认名称为default,ProcessEngines.getDefaultProcessEngine()查询时会先检索main/resources,然后再检索test/resources的activiti.cfg.xml和activiti-context.xml文件,所以当main/resources监测不到指定文件时就会导致该引擎被当做web应用的引擎对象,这样会导致有两个引擎,所以把引擎的名称改为非默认的“default”。
在JVM参数中添加参数:
-Dsun.jnu.encoding=UTF-8 -Dfile.encoding=UTF-8
参考:在Activiti Modeler中设计的流程包含奇数个中文时不能部署问题
在最新的kft-activiti-demo版本(1.7.0)中已经集成了Activiti Modeler,可以在线访问,也可以下载源码学习如何配置。
登录http://demo.kafeitu.me/kft-activiti-demo后选择流程管理->模型工作区菜单项即可。