最近一个新项目要求使用ACTIVITI去做流程控制,找了很多资料踩了不少坑,整理出来,大家共同进步!
首先引入依赖
<!--activiti modeler start-->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter-basic</artifactId>
<exclusions>
<!--把activiti集成的mybatis去掉-->
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
</exclusions>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter-rest-api</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-json-converter</artifactId>
<version>6.0.0</version>
<exclusions>
<exclusion>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-model</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- xml解析依赖-->
<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>
<!-- xml解析依赖-->
其次配置文件
spring:
activiti:
check-process-definitions: false
db-identity-used: false
async-executor-activate: false
# 自动生成Activiti相关表 第一次生成后建议关闭提高运行速度
database-schema-update: false
# 保存历史数据级别设置为full最高级别,便于历史数据的追溯,建议使用
history-level: full
整合activiti官方编辑器
编辑器网上一大把,比较懒也可以用我这一个
springboot集成activiti编辑器-Java文档类资源-CSDN下载
然后配置启动项,这是为了屏蔽掉编辑器自带的登录认证,如果不想屏蔽,登录密码可以再启动信息里找到
@SpringBootApplication(exclude = {org.activiti.spring.boot.SecurityAutoConfiguration.class})
public class DcApplication {
public static void main(String[] args) {
SpringApplication.run(DcApplication.class, args);
System.out.println("运行成功");
}
}
配置接口
import com.dachuang.common.utils.StringUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.Model;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
/**
* @author 刘
* @date 2022/4/28 10:12
*/
@RestController
@RequestMapping("service")
public class ModelRestController {
final String MODEL_ID = "modelId";
final String MODEL_NAME = "name";
final String MODEL_REVISION = "revision";
final String MODEL_DESCRIPTION = "description";
protected static final Logger LOGGER = LoggerFactory.getLogger(ModelRestController.class);
@Autowired
private RepositoryService repositoryService;
@Autowired
private RuntimeService runtimeService;
private ObjectMapper objectMapper = new ObjectMapper();
/**
* 更新流程
*
* @param modelId 模型ID
* @param name 流程模型名称
* @param description
* @param json_xml 流程文件
*/
@PostMapping("/model/{modelId}/save")
@ResponseStatus(value = HttpStatus.OK)
public void saveModel(@PathVariable String modelId
, String name, String description
, String json_xml, String key) {
try {
Model model = repositoryService.getModel(modelId);
ObjectNode modelJson = (ObjectNode) objectMapper.readTree(model.getMetaInfo());
modelJson.put(MODEL_NAME, name);
modelJson.put(MODEL_DESCRIPTION, description);
modelJson.put(MODEL_REVISION, model.getVersion() + 1);
model.setMetaInfo(modelJson.toString());
model.setName(name);
model.setKey(key);
repositoryService.saveModel(model);
// activiti中的id必须以字母或下划线开头
String processId = "process_" + modelId;
// 由于保存的xml所引用的流程id是死得,暂时不知道哪里可以改为自定义,这里以modelId进行替换
json_xml = json_xml.replace("\"process_id\":\"process\"", "\"process_id\":\"" + processId + "\"");
repositoryService.addModelEditorSource(model.getId(), json_xml.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
LOGGER.error("Error saving model", e);
throw new ActivitiException("Error saving model", e);
}
}
/**
* 获取model的节点信息,编辑器根据返回的json进行绘图
*
* @param modelId 模型ID
* @return
*/
@GetMapping("/model/{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());
modelNode.put("key", model.getKey());
ObjectNode editorJsonNode = (ObjectNode) objectMapper.readTree(
new String(repositoryService.getModelEditorSource(model.getId()), "utf-8"));
modelNode.put("model", editorJsonNode);
} catch (Exception e) {
LOGGER.error("Error creating model JSON", e);
throw new ActivitiException("Error creating model JSON", e);
}
}
return modelNode;
}
/**
* 获取编辑器组件及配置项信息
* 获取流程json文件
*
*
*/
@GetMapping("/editor/stencilset")
public String getStencilset() {
InputStream stencilsetStream = this.getClass().getClassLoader().getResourceAsStream("stencilset.json");
try {
return IOUtils.toString(stencilsetStream, "utf-8");
} catch (Exception e) {
throw new ActivitiException("Error while loading stencil set", e);
}
}
/**
* 部署流程
*
* @param modelId 模型ID
*/
@GetMapping("deploy/{modelId}")
public String deployModel(@PathVariable String modelId) throws Exception {
Model modelData = repositoryService.getModel(modelId);
byte[] bytes = repositoryService.getModelEditorSource(modelData.getId());
if (bytes == null) {
throw new Exception("模型数据为空,请先设计流程并成功保存,再进行发布");
}
JsonNode modelNode = new ObjectMapper().readTree(bytes);
BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode);
if (model.getProcesses().size() == 0) {
throw new Exception("数据模型不符要求,请至少设计一条主线流程");
}
byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(model);
//发布流程
String processName = modelData.getName() + ".bpmn20.xml";
Deployment deployment = repositoryService.createDeployment()
.name(modelData.getName())
.addString(processName, new String(bpmnBytes, StandardCharsets.UTF_8))
.deploy();
modelData.setDeploymentId(deployment.getId());
repositoryService.saveModel(modelData);
return processName;
}
@GetMapping("start/{processKey}")
public void startProcess(@PathVariable String processKey) {
runtimeService.startProcessInstanceByKey(processKey);
}
}
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Model;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 模型基本信息创建接口
* @author 刘
* @date 2022/4/28
*/
@Controller
public class ModelController {
private static final Logger logger = LoggerFactory.getLogger(ModelController.class);
@Resource
private RepositoryService repositoryService;
private ObjectMapper objectMapper = new ObjectMapper();
/**
* 创建模型
* @param response
* @param name 模型名称
* @param key 模型key
*/
@GetMapping("/create")
@ResponseBody
public String create(HttpServletResponse response, String name, String key) throws IOException {
logger.info("创建模型入参name:{},key:{}",name,key);
Model model = repositoryService.newModel();
ObjectNode modelNode = objectMapper.createObjectNode();
modelNode.put("name", name);
modelNode.put("description", "");
modelNode.put("revision", 1);
model.setName(name);
model.setKey(key);
model.setMetaInfo(modelNode.toString());
repositoryService.saveModel(model);
createObjectNode(model.getId());
logger.info("创建模型结束,返回模型ID:{}",model.getId());
return model.getId();
}
/**
* 创建模型时完善ModelEditorSource
* @param modelId
*/
private void createObjectNode(String modelId){
logger.info("创建模型完善ModelEditorSource入参模型ID:{}",modelId);
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);
try {
repositoryService.addModelEditorSource(modelId,editorNode.toString().getBytes("utf-8"));
} catch (Exception e) {
logger.info("创建模型时完善ModelEditorSource服务异常:{}",e);
}
logger.info("创建模型完善ModelEditorSource结束");
}
}
启动服务
http://localhost:8080/create?name=xxx&key=xxx 创建一个流程,返回一个流程ID
http://localhost:8080/index.html#/editor/90001 用返回的ID启动编辑器编辑流程
如果想集成的好一点,这两个接口可以合成为一个
这样,一个基础的流程就OK了,之后会更新关于流程的其他使用跟踩坑心得,
感谢观看