Grails + Activiti + Activiti Modeler 整合详解
- 本次整合使用 activiti 的版本为5.22
- 需要下载的文件 war包 和 源码
-
下载jar地址:https://github.com/Activiti/Activiti/releases/tag/activiti-5.22.0
-
下载完成后,将两个zip文件解压
activiti-5.22.0 目录下找到 wars 里面的两个 war包,将它们放到 tomcat 的 webapp 目录下,启动tomcat ,启动后,我们修改解压后的db.db.properties 文件,文件所在目录: activiti-explorer\WEB-INF\classes 下
访问地址:http://localhost:8090/activiti-explorer/ 就可以看见它自身所带的流程前端控制器
在你连接的数据中的 act_id_user 表中,选择一个用户进行登录 就可以看到 它身的 前端流程控制器
搭建属于我们自己的前端流程控制器 grails + activiti 5.22
// https://mvnrepository.com/artifact/org.activiti/activiti-engine
compile group: 'org.activiti', name: 'activiti-engine', version: '5.22.0'
// https://mvnrepository.com/artifact/org.activiti/activiti-spring
compile group: 'org.activiti', name: 'activiti-spring', version: '5.22.0'
// https://mvnrepository.com/artifact/org.activiti/activiti-bpmn-model
compile group: 'org.activiti', name: 'activiti-bpmn-model', version: '5.22.0'
// https://mvnrepository.com/artifact/org.activiti/activiti-json-converter
compile group: 'org.activiti', name: 'activiti-json-converter', version: '5.22.0'
// https://mvnrepository.com/artifact/org.activiti/activiti-bpmn-layout
compile group: 'org.activiti', name: 'activiti-bpmn-layout', version: '5.22.0'
// https://mvnrepository.com/artifact/org.apache.xmlgraphics/batik-transcoder
compile group: 'org.apache.xmlgraphics', name: 'batik-transcoder', version: '1.7'
// https://mvnrepository.com/artifact/org.apache.xmlgraphics/batik-css
compile group: 'org.apache.xmlgraphics', name: 'batik-css', version: '1.7'
// https://mvnrepository.com/artifact/org.apache.xmlgraphics/batik-svg-dom
compile group: 'org.apache.xmlgraphics', name: 'batik-svg-dom', version: '1.7'
// https://mvnrepository.com/artifact/org.apache.xmlgraphics/batik-svggen
compile group: 'org.apache.xmlgraphics', name: 'batik-svggen', version: '1.7'
// https://mvnrepository.com/artifact/org.apache.xmlgraphics/batik-codec
compile group: 'org.apache.xmlgraphics', name: 'batik-codec', version: '1.7'
// https://mvnrepository.com/artifact/mysql/mysql-connector-java
compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.17'
5.将 Activiti-activiti-5.22.0\modules\activiti-webapp-explorer2\src\main\webapp 目录下的两个目录和一个个文件复制到 grails 的webapp 目录下面
- idea 创建一个 grails 项目
- 到 maven 创库中去导入所需要的 activiti 5.22 的 jar
- maven 创库地址:http://mvnrepository.com/
- 需要导入的 jar (activi 工作流引擎的jar 以及 avg 生产图片的 jar)
6.将 Activiti-activiti-5.22.0\modules\activiti-webapp-explorer2\src\main\resources 下的 stencilset.json 复制到 conf 目录下
7.将 Activiti-activiti-5.22.0\modules\activiti-modeler\src\main\java\org\activiti\rest\editor 目录下的 两个目录里面的三个 java 文件取出来
StencilsetRestResource.java : 页面显示资源的获取的接口 修改为 groovy 语法格式
ModelEditorJsonRestResource.java :流程模型资源的获取的接口 修改为groovy 语法格式
ModelSaveRestResource.java :流程模型获取后修改编辑保存的接口 修改为 groovy 语法格式
8.修改后的代码如下:
package com.activiti
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.node.ObjectNode
import grails.converters.JSON
import grails.io.IOUtils
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.ActivitiException
import org.activiti.engine.ProcessEngine
import org.activiti.engine.ProcessEngines
import org.activiti.engine.RepositoryService
import org.activiti.engine.repository.Deployment
import org.activiti.engine.repository.Model
import org.activiti.engine.runtime.ProcessInstance
import org.apache.batik.transcoder.TranscoderInput
import org.apache.batik.transcoder.TranscoderOutput
import org.apache.batik.transcoder.image.PNGTranscoder
import org.apache.commons.lang.StringUtils
class ActivitController {
def index() {
}
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine()
RepositoryService repositoryService = processEngine.getRepositoryService()
ObjectMapper objectMapper = new ObjectMapper()
/**根据Id获取创建的模板信息*/
def getEditorJson(){
def record = request.JSON ?: params
ObjectNode modelNode = null
Model model = repositoryService.getModel(record.id)
if (model != null) {
try {
if (StringUtils.isNotEmpty(model.getMetaInfo())) {
modelNode = (ObjectNode) objectMapper.readTree(model.getMetaInfo())
} else {
modelNode = objectMapper.createObjectNode()
modelNode.put("name", model.getName())
}
modelNode.put("modelId", model.getId())
ObjectNode editorJsonNode = (ObjectNode) objectMapper.readTree(new String(repositoryService.getModelEditorSource(model.getId()), "utf-8"))
modelNode.put("model",editorJsonNode)
} catch (Exception e) {
throw new ActivitiException("Error creating model JSON", e)
}
}
render modelNode
}
/**保存修改的模型*/
def saveModel() {
def record = params
try {
Model model = repositoryService.getModel(record.id)
ObjectNode modelJson = (ObjectNode) objectMapper.readTree(model.getMetaInfo())
modelJson.put("name", record.name);
modelJson.put("description", record.description)
model.setMetaInfo(modelJson.toString())
model.setName(record.name)
repositoryService.saveModel(model)
repositoryService.addModelEditorSource(model.getId(), record.json_xml.getBytes("utf-8"))
InputStream svgStream = new ByteArrayInputStream(record.svg_xml.getBytes("utf-8"))
TranscoderInput input = new TranscoderInput(svgStream)
PNGTranscoder transcoder = new PNGTranscoder()
ByteArrayOutputStream outStream = new ByteArrayOutputStream()
TranscoderOutput output = new TranscoderOutput(outStream)
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)
}
render true
}
def getStencilset() {
InputStream stencilsetStream = this.getClass().getClassLoader().getResourceAsStream("stencilset.json")
try {
render IOUtils.toString(stencilsetStream, "utf-8")
} catch (Exception e) {
throw new ActivitiException("Error while loading stencil set", e)
}
}
/** 新建一个空模型 */
def newModel(){
def record = request.JSON ?: params
def modelId = []
RepositoryService repositoryService = processEngine.getRepositoryService()
//初始化一个空模型
Model model = repositoryService.newModel()
//设置一些默认信息
int revision = 1
String key = "process"
ObjectNode modelNode = objectMapper.createObjectNode()
modelNode.put(ModelDataJsonConstants.MODEL_NAME, record.name)
modelNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, record.description)
modelNode.put(ModelDataJsonConstants.MODEL_REVISION, revision)
model.setName(record.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"))
modelId.add(id)
render modelId
}
/** 获取所有模型 */
def modelList(){
RepositoryService repositoryService = processEngine.getRepositoryService()
render repositoryService.createModelQuery().list() as JSON
}
/** 发布模型为流程定义 */
def deploy(){
def record = request.JSON ?: params
//获取模型
Model modelData = repositoryService.getModel(record.id)
byte[] bytes = repositoryService.getModelEditorSource(modelData.getId())
if (bytes == null) {
render "模型数据为空,请先设计流程并成功保存,再进行发布"
}
JsonNode modelNode = new ObjectMapper().readTree(bytes)
BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode)
if(model.getProcesses().size()==0){
render "数据模型不符要求,请至少设计一条主线流程"
}
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)
render true
}
/**启动流程*/
def startProcess() {
def record = request.JSON ?: params
ProcessInstance process = processEngine.getRuntimeService().startProcessInstanceByKey(record.keyName)
render process.getId() + " : " + process.getProcessDefinitionId()
}
}
9.编辑 访问路径
设置 UrlMappings 访问路径
package activitimodeler
class UrlMappings {
static mappings = {
"/$controller/$action?/$id?(.$format)?"{
constraints {
// apply constraints here
}
}
get "/$controller/$id(.$format)?/json"(action:"getEditorJson")
put "/$controller/$id(.$format)?/save"(action:"saveModel")
get "/$controller/editor/stencilset"(action: "getStencilset")
post "/$controller/create"(action: "newModel")
get "/$controller/modelList"(action: "modelList")
get "/$controller/deploy"(action: "deploy")
get "/$controller/start"(action: "startProcess")
"500"(view:'/error')
"404"(view:'/notFound')
}
}
修改前端访问接口的路径
editor-app目录下的app-cfg.js 文件 'contextRoot' : '/Activit', 设置访问后台接口路径的前缀
editor-app——>configuration——>url-config.js 设置访问路径 接口的路径
editor-app ——>enitor——>oryx.debug.js 设置访问 获取 stencilset.json(前台资源文件) 的路径
最后将 stencilset.json 文件 以及 editor-app\i18n\en.json 文件 变为 中文解释
当这一切都准备好了之后,启动 grails ,启动完成后 访问地址:http://localhost:8080/modeler.html 就可以看到 页面上的信息
代码中的功能涉及:模板创建、获取所有模板、获取单个模板、修改模板、保存修改的模板、发布模型为流程定义、启动流程