JEECG集成ACTIVITI
导航
演示地址
集成处理
后端
POM依赖
<?xml version="1.0"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>d-parent</artifactId>
<version>2.2.0</version>
</parent>
<artifactId>d-twins-activiti</artifactId>
<name>d-activiti</name>
<url>http://maven.apache.org</url>
<properties>
<activiti.version>5.22.0</activiti.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-engine</artifactId>
<version>${activiti.version}</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring</artifactId>
<version>${activiti.version}</version>
<exclusions>
<exclusion>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-modeler</artifactId>
<version>${activiti.version}</version>
<exclusions>
<exclusion>
<artifactId>spring-security-config</artifactId>
<groupId>org.springframework.security</groupId>
</exclusion>
<exclusion>
<artifactId>spring-security-core</artifactId>
<groupId>org.springframework.security</groupId>
</exclusion>
<exclusion>
<artifactId>spring-security-crypto</artifactId>
<groupId>org.springframework.security</groupId>
</exclusion>
<exclusion>
<artifactId>spring-security-web</artifactId>
<groupId>org.springframework.security</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-diagram-rest</artifactId>
<version>${activiti.version}</version>
<exclusions>
<exclusion>
<artifactId>spring-security-config</artifactId>
<groupId>org.springframework.security</groupId>
</exclusion>
<exclusion>
<artifactId>spring-security-core</artifactId>
<groupId>org.springframework.security</groupId>
</exclusion>
<exclusion>
<artifactId>spring-security-crypto</artifactId>
<groupId>org.springframework.security</groupId>
</exclusion>
<exclusion>
<artifactId>spring-security-web</artifactId>
<groupId>org.springframework.security</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- UUID -->
<dependency>
<groupId>com.fasterxml.uuid</groupId>
<artifactId>java-uuid-generator</artifactId>
<version>3.1.3</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.197</version>
</dependency>
</dependencies>
</project>
新增JSON处理过滤器
过滤器的作用是:在查看流程图时,有这两个带callback的请求,而这个过滤器作用是把这两个链接的结果进行处理。假如不这样做,前端看不到对应的流程图形。
package org.jeecg.activiti.filter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class JsonpCallbackFilter implements Filter {
private static final Logger log = LoggerFactory.getLogger(JsonpCallbackFilter.class);
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
Map<String, String[]> parms = httpRequest.getParameterMap();
if (parms.containsKey("callback")) {
if (log.isDebugEnabled())
log.debug("Wrapping response with JSONP callback {}", parms.get("callback")[0]);
OutputStream out = httpResponse.getOutputStream();
GenericResponseWrapper wrapper = new GenericResponseWrapper(httpResponse);
chain.doFilter(request, wrapper);
// handles the content-size truncation
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(new String(parms.get("callback")[0] + "(").getBytes());
outputStream.write(wrapper.getData());
outputStream.write(new String(");").getBytes());
byte jsonpResponse[] = outputStream.toByteArray();
wrapper.setContentType("text/javascript;charset=UTF-8");
wrapper.setContentLength(jsonpResponse.length);
out.write(jsonpResponse);
out.close();
} else {
chain.doFilter(request, response);
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// default implements
}
@Override
public void destroy() {
// default implements
}
}
package org.jeecg.activiti.filter;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
public class GenericResponseWrapper extends HttpServletResponseWrapper {
private ByteArrayOutputStream output;
private int contentLength;
private String contentType;
public GenericResponseWrapper(HttpServletResponse response) {
super(response);
output = new ByteArrayOutputStream();
}
public byte[] getData() {
return output.toByteArray();
}
public ServletOutputStream getOutputStream() {
return new FilterServletOutputStream(output);
}
public PrintWriter getWriter() {
return new PrintWriter(getOutputStream(), true);
}
public void setContentLength(int length) {
this.contentLength = length;
super.setContentLength(length);
}
public int getContentLength() {
return contentLength;
}
public void setContentType(String type) {
this.contentType = type;
super.setContentType(type);
}
public String getContentType() {
return contentType;
}
}
package org.jeecg.activiti.filter;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
public class FilterServletOutputStream extends ServletOutputStream{
private DataOutputStream stream;
@SuppressWarnings("unused")
private WriteListener writeListener;
public FilterServletOutputStream(OutputStream output) {
stream = new DataOutputStream(output);
}
public void write(int b) throws IOException {
stream.write(b);
}
public void write(byte[] b) throws IOException {
stream.write(b);
}
public void write(byte[] b, int off, int len) throws IOException {
stream.write(b, off, len);
}
@Override
public void setWriteListener(WriteListener writeListener) {
this.writeListener = writeListener;
}
@Override
public boolean isReady() {
return true;
}
}
ACTIVITI 配置文件
查看源码可以知道DynamicDataSourceAutoConfiguration是JEECG的动态数据源核心自动配置类
继承该类可以获取数据源DataSource
package org.jeecg.activiti;
import org.activiti.engine.FormService;
import org.activiti.engine.HistoryService;
import org.activiti.engine.IdentityService;
import org.activiti.engine.ManagementService;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.impl.persistence.StrongUuidGenerator;
import org.activiti.spring.ProcessEngineFactoryBean;
import org.activiti.spring.SpringProcessEngineConfiguration;
import org.jeecg.activiti.filter.JsonpCallbackFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.web.context.WebApplicationContext;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration;
@Configuration
public class ActivitiConfiguration extends DynamicDataSourceAutoConfiguration{
@Autowired
private WebApplicationContext webApplicationContext;
@Bean
public FilterRegistrationBean<JsonpCallbackFilter> jsonpCallbackFilter() {
FilterRegistrationBean<JsonpCallbackFilter> frBean = new FilterRegistrationBean<>();
frBean.setFilter(new JsonpCallbackFilter());
frBean.addUrlPatterns("/*");
return frBean;
}
@Bean("uuidGenerator")
public StrongUuidGenerator strongUuidGenerator() {
return new StrongUuidGenerator();
}
@Bean
public PlatformTransactionManager platformTransactionManager () {
return new DataSourceTransactionManager(this.dataSource(this.dynamicDataSourceProvider(this.dynamicDataSourceCreator(webApplicationContext))));
}
@Bean("processEngineConfiguration")
public SpringProcessEngineConfiguration springProcessEngineConfiguration () {
SpringProcessEngineConfiguration processEngineConfiguration = new SpringProcessEngineConfiguration();
processEngineConfiguration.setDataSource(this.dataSource(this.dynamicDataSourceProvider(this.dynamicDataSourceCreator(webApplicationContext))));
processEngineConfiguration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
processEngineConfiguration.setJobExecutorActivate(false);
processEngineConfiguration.setHistory("full");
processEngineConfiguration.setTransactionManager(platformTransactionManager());
processEngineConfiguration.setDbIdentityUsed(true);
processEngineConfiguration.setActivityFontName("\u5B8B\u4F53");
processEngineConfiguration.setLabelFontName("\u5B8B\u4F53");
processEngineConfiguration.setIdGenerator(strongUuidGenerator());
return processEngineConfiguration;
}
@Bean("processEngine")
public ProcessEngineFactoryBean processEngineFactoryBean () {
ProcessEngineFactoryBean engineFactoryBean = new ProcessEngineFactoryBean();
springProcessEngineConfiguration().buildProcessEngine();
engineFactoryBean.setProcessEngineConfiguration(springProcessEngineConfiguration());
return engineFactoryBean;
}
@Bean("repositoryService")
public RepositoryService repositoryService () {
return processEngineFactoryBean().getProcessEngineConfiguration().getRepositoryService();
}
@Bean("runtimeService")
public RuntimeService runtimeService () {
return processEngineFactoryBean().getProcessEngineConfiguration().getRuntimeService();
}
@Bean("historyService")
public HistoryService historyService() {
return processEngineFactoryBean().getProcessEngineConfiguration().getHistoryService();
}
@Bean("taskService")
public TaskService taskService () {
return processEngineFactoryBean().getProcessEngineConfiguration().getTaskService();
}
@Bean("formService")
public FormService formService () {
return processEngineFactoryBean().getProcessEngineConfiguration().getFormService();
}
@Bean("identityService")
public IdentityService identityService() {
return processEngineFactoryBean().getProcessEngineConfiguration().getIdentityService();
}
@Bean("managementService")
public ManagementService managementService() {
return processEngineFactoryBean().getProcessEngineConfiguration().getManagementService();
}
}
启动类扫描org.activiti.rest
查看源码activiti-moderler jar包可以知道:ModelEditorJsonRestResource 是获取流程图JSON的类。以及ModelSaveRestResource是流程保存的类。还有StencilsetRestResource是获取国际化资源的类,假如配置官方的ACTIVITI EDITOR,那么则需要扫描org.activiti.rest,否则moderler.html打开会有404的错误。下图是源码ModelEditorJsonRestResource。
/* Licensed under the Apache License, Version 2.0 (the "License");
package org.activiti.rest.editor.model;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Model;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
/**
* @author Tijs Rademakers
*/
@RestController
public class ModelEditorJsonRestResource implements ModelDataJsonConstants {
protected static final Logger LOGGER = LoggerFactory.getLogger(ModelEditorJsonRestResource.class);
@Autowired
private RepositoryService repositoryService;
@Autowired
private ObjectMapper objectMapper;
@RequestMapping(value="/model/{modelId}/json", method = RequestMethod.GET, produces = "application/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());
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;
}
}
启动类设置,到此后端配置完毕。
@Slf4j
@EnableSwagger2
@SpringBootApplication(
scanBasePackages={
"org.jeecg",
"org.activiti.rest"
})
@EnableScheduling
public class JeecgApplication extends SpringBootServletInitializer{
public static void main(String[] args) throws UnknownHostException {
ConfigurableApplicationContext application = SpringApplication.run(JeecgApplication.class, args);
Environment env = application.getEnvironment();
String ip = InetAddress.getLocalHost().getHostAddress();
String port = env.getProperty("server.port");
String path = env.getProperty("server.servlet.context-path");
log.info("\n----------------------------------------------------------\n\t" +
"Application Jeecg-Boot is running! Access URLs:\n\t" +
"Local: \t\thttp://localhost:" + port + path + "/\n\t" +
"External: \thttp://" + ip + ":" + port + path + "/\n\t" +
"Swagger-UI: \t\thttp://" + ip + ":" + port + path + "/doc.html\n" +
"----------------------------------------------------------");
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(JeecgApplication.class);
}
/**
* tomcat-embed-jasper引用后提示jar找不到的问题
*/
@Bean
public TomcatServletWebServerFactory tomcatFactory() {
return new TomcatServletWebServerFactory() {
@Override
protected void postProcessContext(Context context) {
((StandardJarScanner) context.getJarScanner()).setScanManifest(false);
}
};
}
}
前端
下载资源文件ACTIVITI-EDITOR
下载地址
由于采用前后端分离,这里我们将ACTIVITI-EDITOR 放入public文件夹下
配置url-config.js
作用是配置url
1、 获取json
2、 获取国际化资源stencilset.json、这里我修改了一下 直接从前端获取,不走后台
3、 保存json
启动1、3就是指向我们之前后端扫描org.activiti.rest下的ModelEditorJsonRestResource 、ModelSaveRestResource
/*
* Activiti Modeler component part of the Activiti project
* Copyright 2005-2014 Alfresco Software, Ltd. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
var KISBPM = KISBPM || {};
KISBPM.URL = {
getModel: function(modelId) {
return ACTIVITI.CONFIG.contextRoot + '/model/' + modelId + '/json';
},
getStencilSet: function() {
return '/activiti/stencilset.json?version=' + Date.now();
},
putModel: function(modelId) {
return ACTIVITI.CONFIG.contextRoot + '/model/' + modelId + '/save';
}
};
功能介绍
创建模型
@PostMapping(value = "create")
public Result<?> create(@RequestBody ModelEntity model) {
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.set("stencilset", stencilSetNode);
Model modelData = repositoryService.newModel();
ObjectNode modelObjectNode = objectMapper.createObjectNode();
modelObjectNode.put(ModelDataJsonConstants.MODEL_NAME, model.getName());
modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, 1);
String description = StringUtils.defaultString(model.getMetaInfo());
modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
modelData.setMetaInfo(modelObjectNode.toString());
modelData.setName(model.getName());
modelData.setKey(StringUtils.defaultString(model.getKey()));
repositoryService.saveModel(modelData);
repositoryService.addModelEditorSource(modelData.getId(), editorNode.toString().getBytes("utf-8"));
return Result.ok(modelData.getId());
} catch (Exception e) {
logger.error("创建模型失败:", e);
}
return null;
}
部署流程
@PostMapping(value = "deploy/{modelId}")
public Result<?> deploy(@PathVariable("modelId") String modelId) {
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();
modelData.setDeploymentId(deployment.getId());
repositoryService.saveModel(modelData);
return Result.ok(deployment.getId());
} catch (Exception e) {
logger.error("根据模型部署流程失败:modelId={}", modelId, e);
}
return null;
}
导出模型xml
@GetMapping(value = "export/{modelId}/{type}")
public void export(@PathVariable("modelId") String modelId, @PathVariable("type") String type,
HttpServletResponse response) {
try {
Model modelData = repositoryService.getModel(modelId);
BpmnJsonConverter jsonConverter = new BpmnJsonConverter();
byte[] modelEditorSource = repositoryService.getModelEditorSource(modelData.getId());
JsonNode editorNode = new ObjectMapper().readTree(modelEditorSource);
BpmnModel bpmnModel = jsonConverter.convertToBpmnModel(editorNode);
// 处理异常
if (bpmnModel.getMainProcess() == null) {
response.setStatus(HttpStatus.UNPROCESSABLE_ENTITY.value());
response.getOutputStream().println("no main process, can't export for type: " + type);
response.flushBuffer();
return;
}
String filename = "";
byte[] exportBytes = null;
String mainProcessId = bpmnModel.getMainProcess().getId();
if (type.equals("bpmn")) {
BpmnXMLConverter xmlConverter = new BpmnXMLConverter();
exportBytes = xmlConverter.convertToXML(bpmnModel);
filename = mainProcessId + ".bpmn20.xml";
} else if (type.equals("json")) {
exportBytes = modelEditorSource;
filename = mainProcessId + ".json";
}
ByteArrayInputStream in = new ByteArrayInputStream(exportBytes);
IOUtils.copy(in, response.getOutputStream());
response.setHeader("Content-Disposition", "attachment; filename=" + filename);
response.flushBuffer();
} catch (Exception e) {
logger.error("导出model的xml文件失败:modelId={}, type={}", modelId, type, e);
}
}
删除模型
@DeleteMapping(value = "delete")
public Result<?> delete(@RequestParam(name = "id", required = true) String id) {
repositoryService.deleteModel(id);
return Result.ok("删除成功");
}
上传流程xml
@PostMapping(value = "/uploadXml/{modelId}")
public Result<?> uploadXml(@RequestParam(value = "file", required = false) MultipartFile file,
@PathVariable("modelId") String modelId) {
String fileName = file.getOriginalFilename();
try {
InputStream fileInputStream = file.getInputStream();
Deployment deployment = null;
String extension = FilenameUtils.getExtension(fileName);
if (extension.equals("zip") || extension.equals("bar")) {
ZipInputStream zip = new ZipInputStream(fileInputStream);
deployment = repositoryService.createDeployment().addZipInputStream(zip).deploy();
} else {
deployment = repositoryService.createDeployment().addInputStream(fileName, fileInputStream).deploy();
}
List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery()
.deploymentId(deployment.getId()).list();
String processDefinitionId = list.get(0).getId();
convertToModel(processDefinitionId, modelId);
} catch (Exception e) {
logger.error("error on deploy process, because of file input stream", e);
}
return Result.ok();
}
public void convertToModel(String processDefinitionId, String modelId)
throws UnsupportedEncodingException, XMLStreamException {
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionId(processDefinitionId).singleResult();
InputStream bpmnStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(),
processDefinition.getResourceName());
XMLInputFactory xif = XMLInputFactory.newInstance();
InputStreamReader in = new InputStreamReader(bpmnStream, "UTF-8");
XMLStreamReader xtr = xif.createXMLStreamReader(in);
BpmnModel bpmnModel = new BpmnXMLConverter().convertToBpmnModel(xtr);
BpmnJsonConverter converter = new BpmnJsonConverter();
com.fasterxml.jackson.databind.node.ObjectNode modelNode = converter.convertToJson(bpmnModel);
Model modelData = repositoryService.createModelQuery().modelId(modelId).singleResult();
modelData.setKey(processDefinition.getKey());
modelData.setName(processDefinition.getResourceName());
modelData.setCategory(processDefinition.getDeploymentId());
repositoryService.addModelEditorSource(modelData.getId(), modelNode.toString().getBytes("utf-8"));
}
发起流程
@PostMapping("start")
public Result<?> start(String tableId, String recordId, String modelId,
@RequestParam(value = "title", required = false) String title,
@RequestParam(value = "nextUserId", required = false) String nextUserId, HttpServletRequest request) {
if (ObjectUtils.isEmpty(modelId)) {
return Result.error("modelId为空");
}
ProcessDefinition processDefinition = sysFlowTableService.queryProcessDefinitionKeyByModelId(modelId);
if (ObjectUtils.isEmpty(processDefinition)) {
return Result.error("未绑定流程");
}
String token = TokenUtils.getTokenByRequest(request);
String username = JwtUtil.getUsername(token);
identityService.setAuthenticatedUserId(username);
String businessKey = tableId + "@" + recordId;
// 判断key是否被占用
List<ProcessInstance> instances = runtimeService.createProcessInstanceQuery()
.processInstanceBusinessKey(businessKey).list();
if (!CollectionUtils.isEmpty(instances)) {
return Result.error("业务key已经被占用");
}
if (StringUtils.isEmpty(title)) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
title = String.format("%s于%s发起了流程 %s", username, sdf.format(new Date()),
StringUtils.isEmpty(processDefinition.getName()) ? "" : "[" + processDefinition.getName() + "]");
}
ProcessInstance processInstance = runtimeService.createProcessInstanceBuilder().processInstanceName(title)
.processDefinitionId(processDefinition.getId()).businessKey(businessKey).start();
if (!StringUtils.isEmpty(nextUserId)) {
List<Task> taskList = taskService.createTaskQuery().processInstanceId(processInstance.getId()).list();
for (Task task : taskList) {
taskService.setAssignee(task.getId(), nextUserId);
}
}
Result<String> result = new Result<>();
result.setResult(processInstance.getId());
return result;
}
完成任务
@SuppressWarnings("unchecked")
@PutMapping("complete/{taskId}")
public Result<?> complete(@PathVariable String taskId, @RequestBody Map<String, Object> map) {
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
if (task == null) {
return Result.error("任务不存在");
}
String processInstanceId = task.getProcessInstanceId();
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
identityService.setAuthenticatedUserId(sysUser.getUsername());
Map<String, Object> variables = (Map<String, Object>) map.get("variables");
Object comment = map.get("comment");
if (!ObjectUtils.isEmpty(comment)) {
taskService.addComment(taskId, processInstanceId, comment.toString());
}
Object operation = map.get("operation");
if (!ObjectUtils.isEmpty(operation)) {
taskService.addComment(taskId, processInstanceId, "operation", operation.toString());
}
if (variables != null && !variables.isEmpty()) {
taskService.complete(taskId, variables);
} else {
taskService.complete(taskId);
}
Object nextNodeAssgine = map.get("nextNodeAssgine");
if (!ObjectUtils.isEmpty(nextNodeAssgine)) {
List<Task> list = taskService.createTaskQuery().processInstanceId(processInstanceId).list();
if (!CollectionUtils.isEmpty(list)) {
for (Task t : list) {
taskService.setAssignee(t.getId(), nextNodeAssgine.toString());
}
}
}
Result<String> result = new Result<>();
result.setSuccess(true);
return result;
}
驳回任务
@PutMapping("rejectTaskToPrevious/{taskId}")
public Result<?> rejectTaskToPrevious(@PathVariable String taskId,
@RequestParam(value = "targetId", required = false) String targetId) {
if (StringUtils.isEmpty(targetId)) {
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
if (task == null) {
return Result.error("任务不存在");
}
String processInstanceId = task.getProcessInstanceId();
List<HistoricTaskInstance> historicTaskInstances = historyService.createHistoricTaskInstanceQuery().processInstanceId(processInstanceId).orderByTaskCreateTime().asc().list();
if (historicTaskInstances.size() == 1) {
return Result.error("此节点不支持驳回");
}
String previousTaskId = historicTaskInstances.get(historicTaskInstances.size() - 2).getId();
flowService.rejectTask(taskId, previousTaskId);
} else {
flowService.rejectTask(taskId, targetId);
}
return Result.ok();
}
@Override
public void rejectTask(String currentTaskId, String targetTaskId) {
HistoricTaskInstance targetHistoryTask = historyService.createHistoricTaskInstanceQuery().taskId(targetTaskId).singleResult();
String targetTaskDefinitionId = targetHistoryTask.getTaskDefinitionKey();
ProcessDefinitionImpl definitionImpl = (ProcessDefinitionImpl) repositoryService.getProcessDefinition(targetHistoryTask.getProcessDefinitionId());
ActivityImpl targetActivityImpl = definitionImpl.findActivity(targetTaskDefinitionId);
HistoricTaskInstance currentHistoryTask = historyService.createHistoricTaskInstanceQuery().taskId(currentTaskId).singleResult();
String currentTaskDefinitionId = currentHistoryTask.getTaskDefinitionKey();
ActivityImpl currentActivityImpl = definitionImpl.findActivity(currentTaskDefinitionId);
List<PvmTransition> pvmTransitions = currentActivityImpl.getOutgoingTransitions();
List<PvmTransition> copyTansitions = new ArrayList<>();
for (PvmTransition pvmTransition : pvmTransitions) {
copyTansitions.add(pvmTransition);
}
currentActivityImpl.getOutgoingTransitions().clear();
TransitionImpl transitionImpl = currentActivityImpl.createOutgoingTransition();
transitionImpl.setDestination(targetActivityImpl);
HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(targetHistoryTask.getProcessInstanceId()).singleResult();
// 完成惹怒
taskService.complete(currentTaskId, historicProcessInstance.getProcessVariables());
String assignee = targetHistoryTask.getAssignee();
if (!StringUtils.isEmpty(assignee)) {
Task task = taskService.createTaskQuery().processInstanceId(historicProcessInstance.getId()).singleResult();
taskService.setAssignee(task.getId(), assignee);
}
currentActivityImpl.getOutgoingTransitions().remove(transitionImpl);
for (PvmTransition pvmTransition : copyTansitions) {
currentActivityImpl.getOutgoingTransitions().add(pvmTransition);
}
}
上传附件
@PostMapping("addAttachmentFile/{taskId}")
public Result<?> addAttachmentFile (@RequestParam("file") MultipartFile file, @PathVariable String taskId) {
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
String attachmentType = file.getContentType() + ";" + FilenameUtils.getExtension(file.getOriginalFilename());
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
identityService.setAuthenticatedUserId(sysUser.getUsername());
try {
Attachment attachment = taskService.createAttachment(attachmentType, taskId, task.getProcessInstanceId(), file.getOriginalFilename(), file.getOriginalFilename(), file.getInputStream());
return Result.ok(attachment.getId());
} catch (IOException e) {
logger.error(e.getMessage(), e);
return Result.error("上传失败");
}
}
下载附件
@GetMapping("downloadAttachment/{attachmentId}")
public void downloadAttachment (@PathVariable String attachmentId, HttpServletResponse response) {
Attachment attachment = taskService.getAttachment(attachmentId);
InputStream inputStream = taskService.getAttachmentContent(attachmentId);
String contentType = StringUtils.substringBefore(attachment.getType(), ";");
response.addHeader("Content-Type", contentType + ";charset=UTF-8");
// 根据规则获取文件的扩展名
String extensionFileName = StringUtils.substringAfter(attachment.getType(), ";");
// 以附件名字和扩展名作为下载的文件名
String fileName = attachment.getName() + "." + extensionFileName;
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
try {
IOUtils.copy(new BufferedInputStream(inputStream), response.getOutputStream());
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
任务POJO
@Data
public class TaskModel implements Serializable{
/**
*
*/
private static final long serialVersionUID = -2814330901636164039L;
// 标题
private String title;
// 流程编号
private String processKey;
// 任务Id
private String taskId;
// 流程名称
private String processName;
// 流程实例
private String instanceId;
// 发起人
private String starter;
// 开始时间
private String task_create_time;
// 当前环节
private String taskName;
// 结束时间
private String task_end_time;
// businessKey
private String business_key;
// 当前审批人
private String approver;
// 状态
private String state;
// 到期日
private String dueDate;
// 实例开始时间
private String start_time;
// 实例结束时间
private String end_time;
// 耗时
private String duration;
}
查询发起流程
public Page<TaskModel> queryStart(Map<String, String> params, int start, int limit) {
String starter = params.get("starter");
org.springframework.util.Assert.notNull(starter, "发起人不能为空");
String processName = params.get("flowName");
String processCode = params.get("flowCode");
String state = params.get("state");
HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery();
if (!StringUtils.isEmpty(processName)) {
query.processDefinitionName(processName);
}
if (!StringUtils.isEmpty(processCode)) {
List<ProcessDefinition> definitions = repositoryService.createProcessDefinitionQuery().processDefinitionKeyLike(processCode).list();
List<String> keys = definitions.stream().map(ProcessDefinition::getKey).collect(Collectors.toList());
keys.add(UUID.randomUUID().toString());
query.processDefinitionKeyIn(keys);
}
List<HistoricProcessInstance> historicProcessInstanceList = query.startedBy(starter)
.orderByProcessInstanceStartTime().desc().listPage(start, limit);
long total = query.startedBy(starter).count();
TaskModel taskModel = null;
List<TaskModel> taskList = new ArrayList<>();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for (HistoricProcessInstance historicProcessInstance : historicProcessInstanceList) {
taskModel = new TaskModel();
taskModel.setProcessKey(historicProcessInstance.getProcessDefinitionId());
taskModel.setProcessName(historicProcessInstance.getProcessDefinitionName());
if (!ObjectUtils.isEmpty(historicProcessInstance.getStartTime())) {
taskModel.setStart_time(sdf.format(historicProcessInstance.getStartTime()));
}
if (!ObjectUtils.isEmpty(historicProcessInstance.getEndTime())) {
taskModel.setEnd_time(sdf.format(historicProcessInstance.getEndTime()));
}
taskModel.setInstanceId(historicProcessInstance.getId());
taskModel.setBusiness_key(historicProcessInstance.getBusinessKey());
taskModel.setStarter(starter);
taskModel.setProcessName(historicProcessInstance.getProcessDefinitionName());
taskModel.setTitle(historicProcessInstance.getName());
if (!ObjectUtils.isEmpty(historicProcessInstance.getEndTime())) {
taskModel.setState("已结束");
taskModel.setEnd_time(sdf.format(historicProcessInstance.getEndTime()));
taskModel.setDuration(TimeUtils.getTime(historicProcessInstance.getEndTime(), historicProcessInstance.getStartTime()));
if (StringUtils.isEmpty(state) || "已结束".equals(state)) {
taskList.add(taskModel);
}
} else {
List<Task> task = taskService.createTaskQuery().processInstanceId(historicProcessInstance.getId())
.list();
for (Task taskItem : task) {
taskModel.setState("运行中");
taskModel.setTaskName(taskItem.getName());
if (!ObjectUtils.isEmpty(taskItem.getDueDate())) {
taskModel.setDueDate(sdf.format(taskItem.getDueDate()));
}
if (!ObjectUtils.isEmpty(taskItem.getCreateTime())) {
taskModel.setTask_create_time(sdf.format(taskItem.getCreateTime()));
}
taskModel.setDuration(TimeUtils.getTime(new Date(), historicProcessInstance.getStartTime()));
taskModel.setTaskId(taskItem.getId());
TaskModel copyModel = new TaskModel();
BeanUtils.copyProperties(taskModel, copyModel);
if (StringUtils.isEmpty(state) || "运行中".equals(state)) {
taskList.add(copyModel);
}
}
}
}
Page<TaskModel> page = new Page<TaskModel>();
page.setTotal(total);
page.setRecords(taskList);
return page;
}
查询待办
public Page<TaskModel> queryTask(Map<String, String> params, int start, int limit) {
String assgineId = params.get("assgineId");
String processName = params.get("processName");
String starterId = params.get("starterId");
String isGroupTask = params.get("isGroupTask");
NativeTaskQuery nativeTaskQuery = taskService.createNativeTaskQuery();
StringBuilder filter = new StringBuilder();
// 审批人
if (!StringUtils.isEmpty(assgineId)) {
// 是否是组任务
if ("1".equals(isGroupTask)) {
filter.append(
" AND ((A.ASSIGNEE_ IS NULL AND (B.USER_ID_ = #{assgineId} OR B.GROUP_ID_ IN (SELECT GROUP_ID_ FROM ACT_ID_MEMBERSHIP WHERE USER_ID_ = #{assgineId}))))");
} else {
filter.append(" AND ( A.ASSIGNEE_ = #{assgineId}) ");
}
nativeTaskQuery.parameter("assgineId", assgineId);
}
// 流程名称
if (!StringUtils.isEmpty(processName)) {
filter.append(" AND D.NAME_ LIKE #{processName} ");
nativeTaskQuery.parameter("processName", "%" + processName + "%");
}
// 发起人
if (!StringUtils.isEmpty(starterId)) {
filter.append(" AND E.START_USER_ID_ = #{starterId} ");
nativeTaskQuery.parameter("starterId", starterId);
}
StringBuilder sql = new StringBuilder();
sql.append(" SELECT A.* FROM ACT_RU_TASK A ");
// 是否是组任务
if ("1".equals(isGroupTask)) {
sql.append(" LEFT JOIN ACT_RU_IDENTITYLINK B ON B.TASK_ID_ = A.ID_ ");
sql.append(" LEFT JOIN ACT_RU_IDENTITYLINK C ON C.PROC_INST_ID_ = A.PROC_INST_ID_ ");
}
sql.append(" LEFT JOIN ACT_RU_EXECUTION D ON A.EXECUTION_ID_ = D.ID_ ");
sql.append(" LEFT JOIN ACT_HI_PROCINST E ON A.PROC_INST_ID_ = E.PROC_INST_ID_ ");
sql.append(" WHERE 1=1 ");
if (!StringUtils.isEmpty(filter.toString())) {
sql.append(filter);
}
sql.append(" order by A.CREATE_TIME_ DESC ");
nativeTaskQuery.sql(sql.toString());
List<Task> tasks = nativeTaskQuery.listPage(start, limit);
List<TaskModel> taskList = new ArrayList<>();
TaskModel taskModel = null;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for (Task task : tasks) {
taskModel = new TaskModel();
String instanceId = task.getProcessInstanceId();
HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
.processInstanceId(instanceId).singleResult();
taskModel.setTitle(historicProcessInstance.getName());
taskModel.setProcessKey(historicProcessInstance.getProcessDefinitionKey());
taskModel.setTaskId(task.getId());
taskModel.setProcessName(historicProcessInstance.getProcessDefinitionName());
if (!ObjectUtils.isEmpty(task.getCreateTime())) {
taskModel.setTask_create_time(sdf.format(task.getCreateTime()).toString());
}
taskModel.setInstanceId(instanceId);
String startId = historicProcessInstance.getStartUserId();
taskModel.setStarter(startId);
taskModel.setTaskName(task.getName());
taskList.add(taskModel);
}
String countSql = String.format("SELECT COUNT(*) FROM (%s) tmp; ", sql.toString());
long count = nativeTaskQuery.sql(countSql).count();
Page<TaskModel> page = new Page<TaskModel>();
page.setTotal(count);
page.setRecords(taskList);
return page;
}
查询已办
public Page<TaskModel> queryApproved(Map<String, String> params, int start, int limit) {
String flowName = params.get("flowName");
NativeHistoricProcessInstanceQuery query = historyService.createNativeHistoricProcessInstanceQuery();
StringBuilder sb = new StringBuilder();
sb.append(" select A.* from ACT_HI_PROCINST A where 1=1 ");
StringBuilder filters = new StringBuilder();
if (!StringUtils.isEmpty(flowName)) {
filters.append(" and A.NAME_ like #{processName} ");
query.parameter("processName", flowName);
}
sb.append(filters.toString());
sb.append(" ORDER BY A.START_TIME_ DESC ");
query.sql(sb.toString());
List<HistoricProcessInstance> executions = query.listPage(start, limit);
List<TaskModel> taskList = new ArrayList<>();
TaskModel taskModel = null;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for (HistoricProcessInstance historicProcessInstance : executions) {
taskModel = new TaskModel();
String processInstanceId = historicProcessInstance.getId();
String processDefinitionId = historicProcessInstance.getProcessDefinitionId();
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionId(processDefinitionId).singleResult();
taskModel.setProcessName(processDefinition.getName());
if (!ObjectUtils.isEmpty(historicProcessInstance.getStartTime())) {
taskModel.setStart_time(sdf.format(historicProcessInstance.getStartTime()));
}
taskModel.setTitle(historicProcessInstance.getName());
taskModel.setProcessKey(historicProcessInstance.getProcessDefinitionKey());
taskModel.setProcessName(historicProcessInstance.getProcessDefinitionName());
taskModel.setInstanceId(processInstanceId);
taskModel.setStarter(historicProcessInstance.getStartUserId());
if (ObjectUtils.isEmpty(historicProcessInstance.getEndTime())) {
List<Task> list = taskService.createTaskQuery().processInstanceId(processInstanceId).list();
for (Task task : list) {
TaskModel copyTask = new TaskModel();
BeanUtils.copyProperties(taskModel, copyTask);
copyTask.setTaskId(task.getId());
copyTask.setTaskName(task.getName());
if (!ObjectUtils.isEmpty(task.getCreateTime())) {
copyTask.setTask_create_time(sdf.format(task.getCreateTime()));
}
copyTask.setApprover(task.getAssignee());
taskList.add(copyTask);
}
} else {
taskList.add(taskModel);
}
}
Page<TaskModel> page = new Page<TaskModel>();
page.setTotal(query.sql(String.format("select count(*) from (%s) tmp ", sb.toString())).count());
page.setRecords(taskList);
return page;
}