Flowable入门

1) flowable整合springboot
2) 移除flowable权限校验,加入微服务项目本身权限校验
3) 常用的API接口(查看流程图、部署流程、我的待办、我的已办、完结任务、审批历史等)

1. pom文件引入Flowable相关架包

目前这里引入的是6.4.0版本的flowable


<dependency><groupId>org.flowable</groupId><artifactId>flowable-spring-boot-starter</artifactId><version>${flowable.version}</version><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-log4j2</artifactId></exclusion><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions></dependency><!-- 配置文件处理器 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId></dependency><!-- app 依赖 包含 rest,logic,conf --><!-- flowable 集成依赖 rest,logic,conf --><dependency><groupId>org.flowable</groupId><artifactId>flowable-ui-modeler-rest</artifactId><version>${flowable.version}</version></dependency><dependency><groupId>org.flowable</groupId><artifactId>flowable-ui-modeler-logic</artifactId><version>${flowable.version}</version><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-log4j2</artifactId></exclusion><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.flowable</groupId><artifactId>flowable-ui-modeler-conf</artifactId><version>${flowable.version}</version></dependency><dependency><groupId>org.flowable</groupId><artifactId>flowable-ldap-configurator</artifactId><version>${flowable.version}</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.10</version></dependency>

2.用户角色权限整合

2.1 AppDispatcherServletConfiguration


import org.flowable.ui.modeler.rest.app.EditorGroupsResource;
import org.flowable.ui.modeler.rest.app.EditorUsersResource;
import org.flowable.ui.modeler.rest.app.StencilSetResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;@Configuration
@ComponentScan(value = { "org.flowable.ui.modeler.rest.app",// 不加载 rest,因为 getAccount 接口需要我们自己实现
//        "org.flowable.ui.common.rest"},excludeFilters = {// 移除 EditorUsersResource 与 EditorGroupsResource,因为不使用 IDM 部分@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = EditorUsersResource.class),@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = EditorGroupsResource.class),// 配置文件用自己的@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = StencilSetResource.class),}
)
@EnableAsync
public class AppDispatcherServletConfiguration implements WebMvcRegistrations {private static final Logger LOGGER = LoggerFactory.getLogger(AppDispatcherServletConfiguration.class);@Beanpublic SessionLocaleResolver localeResolver() {return new SessionLocaleResolver();}@Beanpublic LocaleChangeInterceptor localeChangeInterceptor() {LOGGER.debug("Configuring localeChangeInterceptor");LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();localeChangeInterceptor.setParamName("language");return localeChangeInterceptor;}@Overridepublic RequestMappingHandlerMapping getRequestMappingHandlerMapping() {LOGGER.debug("Creating requestMappingHandlerMapping");RequestMappingHandlerMapping requestMappingHandlerMapping = new RequestMappingHandlerMapping();requestMappingHandlerMapping.setUseSuffixPatternMatch(false);requestMappingHandlerMapping.setRemoveSemicolonContent(false);Object[] interceptors = { localeChangeInterceptor() };requestMappingHandlerMapping.setInterceptors(interceptors);return requestMappingHandlerMapping;}
}

2.2 ApplicationConfiguration


import org.flowable.ui.common.service.idm.RemoteIdmService;
import org.flowable.ui.modeler.properties.FlowableModelerAppProperties;
import org.flowable.ui.modeler.servlet.ApiDispatcherServletConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;@Configuration
@EnableConfigurationProperties(FlowableModelerAppProperties.class)
@ComponentScan(basePackages = {//        "org.flowable.ui.modeler.conf","org.flowable.ui.modeler.repository","org.flowable.ui.modeler.service",
//        "org.flowable.ui.modeler.security", //授权方面的都不需要
//        "org.flowable.ui.common.conf", // flowable 开发环境内置的数据库连接
//        "org.flowable.ui.common.filter", // IDM 方面的过滤器"org.flowable.ui.common.service","org.flowable.ui.common.repository",//
//        "org.flowable.ui.common.security",//授权方面的都不需要"org.flowable.ui.common.tenant" },excludeFilters = {// 移除 RemoteIdmService@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = RemoteIdmService.class),}
)
public class ApplicationConfiguration {@Beanpublic ServletRegistrationBean modelerApiServlet(ApplicationContext applicationContext) {AnnotationConfigWebApplicationContext dispatcherServletConfiguration = new AnnotationConfigWebApplicationContext();dispatcherServletConfiguration.setParent(applicationContext);dispatcherServletConfiguration.register(ApiDispatcherServletConfiguration.class);DispatcherServlet servlet = new DispatcherServlet(dispatcherServletConfiguration);ServletRegistrationBean registrationBean = new ServletRegistrationBean(servlet, "/api/*");registrationBean.setName("Flowable Modeler App API Servlet");registrationBean.setLoadOnStartup(1);registrationBean.setAsyncSupported(true);return registrationBean;}
}

2.3 FlowableBeanConfig

@Configuration
public class FlowableBeanConfig {protected static final String LIQUIBASE_CHANGELOG_PREFIX = "ACT_DE_";@Beanpublic Liquibase liquibase(DataSource dataSource) {try {DatabaseConnection connection = new JdbcConnection(dataSource.getConnection());Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(connection);database.setDatabaseChangeLogTableName(LIQUIBASE_CHANGELOG_PREFIX + database.getDatabaseChangeLogTableName());database.setDatabaseChangeLogLockTableName(LIQUIBASE_CHANGELOG_PREFIX + database.getDatabaseChangeLogLockTableName());Liquibase liquibase = new Liquibase("META-INF/liquibase/flowable-modeler-app-db-changelog.xml", new ClassLoaderResourceAccessor(), database);liquibase.update("flowable");return liquibase;} catch (Exception e) {throw new RuntimeException("Error creating liquibase database", e);}}
}

2.4 SecurityUtils


import com.bho.model.LoginUser;
import com.bho.utils.SysUserUtils;
import org.flowable.idm.api.User;
import org.flowable.ui.common.model.RemoteUser;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;import java.util.ArrayList;
import java.util.List;/*** Utility class for Spring Security.*/
public class SecurityUtils {private static User assumeUser;private SecurityUtils() {}/*** Get the login of the current user.*/public static String getCurrentUserId() {User user = getCurrentUserObject();if (user != null) {return user.getId();}return null;}/*** @return the {@link User} object associated with the current logged in user.*/public static User getCurrentUserObject() {LoginUser currentLoginUser = SysUserUtils.getCurrentLoginUser();RemoteUser user = new RemoteUser();user.setId(String.valueOf(currentLoginUser.getId()));user.setFirstName(currentLoginUser.getAccount());List<String> pris = new ArrayList<>();pris.add(DefaultPrivileges.ACCESS_MODELER);pris.add(DefaultPrivileges.ACCESS_IDM);pris.add(DefaultPrivileges.ACCESS_ADMIN);pris.add(DefaultPrivileges.ACCESS_TASK);pris.add(DefaultPrivileges.ACCESS_REST_API);user.setPrivileges(pris);return user;
//        if (assumeUser != null) {//            return assumeUser;
//        }
//
//        RemoteUser user = new RemoteUser();
//        user.setId("admin");
//        user.setDisplayName("Administrator");
//        user.setFirstName("Administrator");
//        user.setLastName("Administrator");
//        user.setEmail("admin@flowable.com");
//        user.setPassword("123456");
//        List<String> pris = new ArrayList<>();
//        pris.add(DefaultPrivileges.ACCESS_MODELER);
//        pris.add(DefaultPrivileges.ACCESS_IDM);
//        pris.add(DefaultPrivileges.ACCESS_ADMIN);
//        pris.add(DefaultPrivileges.ACCESS_TASK);
//        pris.add(DefaultPrivileges.ACCESS_REST_API);
//        user.setPrivileges(pris);
//        return user;}public static FlowableAppUser getCurrentFlowableAppUser() {FlowableAppUser user = null;SecurityContext securityContext = SecurityContextHolder.getContext();if (securityContext != null && securityContext.getAuthentication() != null) {Object principal = securityContext.getAuthentication().getPrincipal();if (principal instanceof FlowableAppUser) {user = (FlowableAppUser) principal;}}return user;}public static boolean currentUserHasCapability(String capability) {FlowableAppUser user = getCurrentFlowableAppUser();for (GrantedAuthority grantedAuthority : user.getAuthorities()) {if (capability.equals(grantedAuthority.getAuthority())) {return true;}}return false;}public static void assumeUser(User user) {assumeUser = user;}public static void clearAssumeUser() {assumeUser = null;}}

3.modeler编辑器汉化

3.1 引入汉化文件

3.2 重写相关接口


import com.bho.model.BaseException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/app")
public class FlowableStencilSetResourceController {private static final Logger LOGGER = LoggerFactory.getLogger(FlowableStencilSetResourceController.class);@Autowiredprotected ObjectMapper objectMapper;@RequestMapping(value = "/rest/stencil-sets/editor", method = RequestMethod.GET, produces = "application/json")public JsonNode getStencilSetForEditor() {try {JsonNode stencilNode = objectMapper.readTree(this.getClass().getClassLoader().getResourceAsStream("stencilset/stencilset_bpmn.json"));return stencilNode;} catch (Exception e) {LOGGER.error("Error reading bpmn stencil set json", e);throw new BaseException("Error reading bpmn stencil set json");}}@RequestMapping(value = "/rest/stencil-sets/cmmneditor", method = RequestMethod.GET, produces = "application/json")public JsonNode getCmmnStencilSetForEditor() {try {JsonNode stencilNode = objectMapper.readTree(this.getClass().getClassLoader().getResourceAsStream("stencilset/stencilset_cmmn.json"));return stencilNode;} catch (Exception e) {LOGGER.error("Error reading bpmn stencil set json", e);throw new BaseException("Error reading bpmn stencil set json");}}
}

4.引入静态资源文件

4.1 将静态资源文件整个目录拷贝到自己项目中

4.2 页面接口添加请求头和权限判断

修改augular.js文件


修改index.html

4.3 修改接口请求路径

修改app-cfg.js文件

4.4 增加表单属性类型(可选操作)

因为flowable自带的表单属性类型有限,不够支撑业务需求,所以增加了几种类型,方便后面动态展示表单。

4.5 修改节点用户和角色




重写用户角色接口


import com.bho.model.LoginUser;
import com.bho.model.RestResponse;
import com.bho.user.feign.SysRoleApi;
import com.bho.user.feign.SysUserApi;
import com.bho.user.form.SysRoleDto;
import com.bho.user.form.SysUserDto;
import com.bho.utils.SysUserUtils;
import org.flowable.ui.common.model.GroupRepresentation;
import org.flowable.ui.common.model.ResultListDataRepresentation;
import org.flowable.ui.common.model.UserRepresentation;
import org.flowable.ui.common.security.DefaultPrivileges;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import java.util.ArrayList;
import java.util.List;@RestController
@RequestMapping("app")
public class UserAndGroupResourceController {@Autowiredprivate SysUserApi sysUserApi;@Autowiredprivate SysRoleApi sysRoleApi;@RequestMapping(value = "/rest/editor-groups", method = RequestMethod.GET)public ResultListDataRepresentation getGroups(@RequestParam(required = false, value = "filter") String filter) {RestResponse<List<SysRoleDto>> restResponse = sysRoleApi.findRoleInfoByName(filter);if(restResponse.getCode() == RestResponse.SUCCESS_CODE){List<SysRoleDto> sysRoleDtoList = restResponse.getData();List<GroupRepresentation> representationList = new ArrayList<>(sysRoleDtoList.size());for(SysRoleDto sysRoleDto : sysRoleDtoList) {GroupRepresentation representation = new GroupRepresentation();representation.setName(sysRoleDto.getName());representation.setId(String.valueOf(sysRoleDto.getId()));representationList.add(representation);}return new ResultListDataRepresentation(representationList);}return new ResultListDataRepresentation();}@RequestMapping(value = "/rest/editor-users", method = RequestMethod.GET)public ResultListDataRepresentation getUsers(@RequestParam(value = "filter", required = false) String filter) {RestResponse<List<SysUserDto>> restResponse = sysUserApi.findUserInfoByName(filter);if(restResponse.getCode() == RestResponse.SUCCESS_CODE){List<SysUserDto> sysUserDtoList = restResponse.getData();List<UserRepresentation> userRepresentationList = new ArrayList<>(sysUserDtoList.size());for(SysUserDto sysUserDto : sysUserDtoList) {UserRepresentation userRepresentation = new UserRepresentation();userRepresentation.setEmail(sysUserDto.getEmail());userRepresentation.setFirstName(sysUserDto.getUsername());userRepresentation.setId(String.valueOf(sysUserDto.getId()));userRepresentationList.add(userRepresentation);}return new ResultListDataRepresentation(userRepresentationList);}return new ResultListDataRepresentation();}/*** 获取默认的管理员信息* @return*/@RequestMapping(value = "/rest/account", method = RequestMethod.GET, produces = "application/json")public UserRepresentation getAccount() {LoginUser currentLoginUser = SysUserUtils.getCurrentLoginUser();UserRepresentation user = new UserRepresentation();user.setId(String.valueOf(currentLoginUser.getId()));user.setFirstName(currentLoginUser.getAccount());List<String> pris = new ArrayList<>();pris.add(DefaultPrivileges.ACCESS_MODELER);pris.add(DefaultPrivileges.ACCESS_IDM);pris.add(DefaultPrivileges.ACCESS_ADMIN);pris.add(DefaultPrivileges.ACCESS_TASK);pris.add(DefaultPrivileges.ACCESS_REST_API);user.setPrivileges(pris);return user;}
}

效果图

4.6 其它一些样式调整

5.用例Demo

这里拿维修工单来举例说明

5.1 流程模型

5.1.1 后端代码


import com.bho.flowable.dto.ModelResp;
import com.bho.flowable.form.ReqFormParam;
import com.bho.flowable.mapper.ActCommonMapper;
import com.bho.flowable.service.ActDeModelService;
import com.bho.utils.SysUserUtils;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Deployment;
import org.flowable.ui.modeler.domain.Model;
import org.flowable.ui.modeler.model.ModelRepresentation;
import org.flowable.ui.modeler.serviceapi.ModelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.util.Date;
import java.util.List;/*** @description:* @author:wanzh* @date:2021/7/2*/
@Service
public class ActDeModelServiceImpl  implements ActDeModelService {private static final String BPMN_SUFFIX = ".bpmn";@Autowiredprivate ModelService modelService;@Autowiredprivate RepositoryService repositoryService;@Autowiredprivate ActCommonMapper actCommonMapper;@Override@Transactionalpublic String deployment(String modelId) {Model model = modelService.getModel(modelId);BpmnModel bpmnModel = modelService.getBpmnModel(model);Deployment deployment = repositoryService.createDeployment().name(model.getName()).addBpmnModel(model.getKey() + BPMN_SUFFIX, bpmnModel).deploy();String deploymentId = deployment.getId();return deploymentId;   //部署ID}@Overridepublic int delete(String key) {modelService.deleteModel(key);return 0;}@Overridepublic void add(Model model) {String userId = String.valueOf(SysUserUtils.getCurrentLoginUser().getId());model.setCreatedBy(userId);model.setLastUpdatedBy(userId);Date date = new Date();model.setCreated(date);model.setLastUpdated(date);ModelRepresentation modelRepresentation = new ModelRepresentation();modelRepresentation.setKey(model.getKey());modelRepresentation.setName(model.getName());modelRepresentation.setModelType(model.getModelType());String json = modelService.createModelJson(modelRepresentation);model.setModelEditorJson(json);model.setVersion(1);modelService.createModel(model,null);}/**提供的SQL查询语句<select id="findModel" resultType="com.bho.flowable.dto.ModelResp">select id,name,model_key as "key",created,version from act_de_model  where model_type = 0<if test="name != null and name != ''">and name  like  CONCAT('%', #{name}, '%')</if><if test="key != null and key != ''">and model_key  like  CONCAT('%', #{key}, '%')</if></select>*/@Overridepublic PageInfo findObjectByPage(ReqFormParam reqFormParam) {PageHelper.startPage(reqFormParam.getPageNum(), reqFormParam.getPageSize()); // , trueList<ModelResp> modelResps = actCommonMapper.findModel(reqFormParam.getTitle(),reqFormParam.getName(),reqFormParam.getKey());return new PageInfo(modelResps);}
}

5.1.2 前端页面

添加

编辑(画流程图、配置用户/组、设置节点表单)

设置故障处置节点表单属性选择的维修人员

接单节点的用户配置跟表单属性保持一致

需要跳过的节点设置${skip}即可

这里不一一介绍每个节点是如何配置的,配置完毕点击部署按钮即可生成流程定义。

5.2 流程定义

5.2.1 后端代码


import com.bho.flowable.dto.ProcessDefinitionResp;
import com.bho.flowable.form.ReqFormParam;
import com.bho.flowable.service.ActReProcdefService;
import com.bho.model.BaseException;
import com.bho.utils.BeanTransferUtils;
import com.github.pagehelper.PageInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.repository.ProcessDefinitionQuery;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;/*** @description:* @author:wanzh* @date:2021/7/2*/
@Slf4j
@Service
public class ActReProcdefServiceImpl  implements ActReProcdefService {@Autowiredprivate RepositoryService repositoryService;@Overridepublic void activate(String id) {ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(id).singleResult();if(processDefinition == null){throw new BaseException("当前流程不存在");}if(processDefinition.isSuspended()){repositoryService.activateProcessDefinitionById(id, true, null);}else {throw new BaseException("当前流程已被激活,请不要重复操作");}}@Overridepublic void suspend(String id) {ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(id).singleResult();if(processDefinition == null){throw new BaseException("当前流程不存在");}if(!processDefinition.isSuspended()){//processDefinitionKey流程定义的id(key)// suspendProcessInstances:是否级联挂起该流程定义下的流程实例// suspensionDate:设置挂起这个流程定义的时间,如果不填写,则立即挂起repositoryService.suspendProcessDefinitionById(id, true, null);}else{throw new BaseException("当前流程已被挂起,请不要重复操作");}}@Overridepublic void viewImage(OutputStream out, String id) {try {InputStream in = repositoryService.getProcessDiagram(id);IOUtils.copy(in, out);}catch (Exception e){log.error("获取流程图出错:{}",e);throw new BaseException("获取流程图出错");}}@Overridepublic int delete(String key) {ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(key).singleResult();if(processDefinition == null){throw new BaseException("当前流程不存在");}repositoryService.deleteDeployment(processDefinition.getDeploymentId(), true);return 0;}@Overridepublic PageInfo findObjectByPage(ReqFormParam reqFormParam) {PageInfo pageInfo = new PageInfo();pageInfo.setPageSize(reqFormParam.getPageSize());pageInfo.setPageNum(reqFormParam.getPageNum());ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();if(!StringUtils.isEmpty(reqFormParam.getName())){processDefinitionQuery = processDefinitionQuery.processDefinitionNameLike(reqFormParam.getName());}if(!StringUtils.isEmpty(reqFormParam.getKey())){processDefinitionQuery = processDefinitionQuery.processDefinitionKeyLike(reqFormParam.getKey());}long count = processDefinitionQuery.count();pageInfo.setTotal(count);if(count > 0){List<ProcessDefinition> processDefinitions = processDefinitionQuery.orderByProcessDefinitionVersion().desc().listPage((reqFormParam.getPageNum() - 1) * reqFormParam.getPageSize(), reqFormParam.getPageSize());List<ProcessDefinitionResp> processDefinitionResps = BeanTransferUtils.doToDtoList(processDefinitions, ProcessDefinitionResp.class);pageInfo.setList(processDefinitionResps);}return pageInfo;}
}

防止流程图中文乱码

import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
import org.springframework.context.annotation.Configuration;@Configuration
public class FlowableConfig implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> {@Overridepublic void configure(SpringProcessEngineConfiguration engineConfiguration) {engineConfiguration.setActivityFontName("宋体");engineConfiguration.setLabelFontName("宋体");engineConfiguration.setAnnotationFontName("宋体");engineConfiguration.setDrawSequenceFlowNameWithNoLabelDI(true);}}

5.2.2 前端页面


5.3 流程实例

5.3.1 后端代码

提供一个发起申请接口供其它微服务调用


import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;import javax.validation.constraints.NotBlank;/*** @description:* @author:wanzh* @date:2021/7/14*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ProcessInstanceDto {private String variable;//变量 json字符串private String callbackUrl;//回调地址private String method;//方法@NotBlank(message = "流程标识不能为空")private String processKey;//流程标识@NotBlank(message = "业务标识不能为空")private String businessKey;//业务标识private String additionTitle;//附加标题private String serialNo;//流水号}
import com.bho.flowable.dto.ProcessInstanceDto;
import com.bho.model.RestResponse;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;import javax.validation.Valid;/*** @description:* @author:wanzh* @date:2021/7/9*/
@FeignClient(name = "iot-flowable")
public interface ProcessInstanceApi {/*** 发起流程实例* @param processInstanceDto* @return*/@PostMapping("/rest/processInstance/start")RestResponse<String> start(@Valid  @RequestBody ProcessInstanceDto processInstanceDto);}

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import com.bho.flowable.dto.ProcessInstanceDto;
import com.bho.flowable.feign.ProcessInstanceApi;
import com.bho.model.BaseException;
import com.bho.model.LoginUser;
import com.bho.model.RestResponse;
import com.bho.utils.JacksonUtil;
import com.bho.utils.SysUserUtils;
import lombok.extern.slf4j.Slf4j;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.runtime.ProcessInstanceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RestController;import java.util.Date;
import java.util.HashMap;
import java.util.Map;/*** @description:* @author:wanzh* @date:2021/7/14*/
@Slf4j
@RestController
public class ProcessInstanceApiImpl implements ProcessInstanceApi{@Autowiredprivate RuntimeService runtimeService;@Autowiredprivate RepositoryService repositoryService;@Overridepublic RestResponse<String> start(ProcessInstanceDto processInstanceDto) {LoginUser loginUser = SysUserUtils.getCurrentLoginUser();Authentication.setAuthenticatedUserId(String.valueOf(loginUser.getId()));String json = processInstanceDto.getVariable();Map<String,Object> variable = new HashMap<>();if(!StringUtils.isEmpty(json)){variable = JacksonUtil.jsonToMap(json,String.class,Object.class);}variable.put("_ACTIVITI_SKIP_EXPRESSION_ENABLED", true);variable.put("skip",true);ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionKey(processInstanceDto.getProcessKey()).active().latestVersion().singleResult();if(processDefinition == null){throw new BaseException("未知的流程标识");}String title = loginUser.getUserName() + "在" + DateUtil.format(new Date(),DatePattern.NORM_DATE_PATTERN) +"发起"+processDefinition.getName()+ (StringUtils.isEmpty(processInstanceDto.getSerialNo()) ? "" : processInstanceDto.getSerialNo())+ (StringUtils.isEmpty(processInstanceDto.getAdditionTitle()) ? "" :  "【"+processInstanceDto.getAdditionTitle()+"】");ProcessInstanceBuilder processInstanceBuilder = runtimeService.createProcessInstanceBuilder().name(title)//流程标题.businessKey(String.valueOf(processInstanceDto.getBusinessKey()))//业务主键.processDefinitionKey(processInstanceDto.getProcessKey())//流程标识.callbackId(processInstanceDto.getCallbackUrl()).callbackType(StringUtils.isEmpty(processInstanceDto.getMethod()) ? "POST" : processInstanceDto.getMethod()).variables(variable);//流程变量// 启动(即创建)流程实例ProcessInstance processInstance = processInstanceBuilder.start();Authentication.setAuthenticatedUserId(null);log.info("instanceId:{}",processInstance.getProcessInstanceId());return RestResponse.buildSuccessData(processInstance.getProcessInstanceId());}}

常用接口


import com.bho.flowable.dto.ApprovalHistoryResp;
import com.bho.flowable.dto.OpTypeEnum;
import com.bho.flowable.dto.ProcessInstanceResp;
import com.bho.flowable.form.ReqFormParam;
import com.bho.flowable.mapper.ActCommonMapper;
import com.bho.flowable.service.ActInstanceService;
import com.bho.flowable.utils.FlowableConstants;
import com.bho.model.BaseException;
import com.bho.utils.CommonConst;
import com.bho.utils.CommonUtils;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowNode;
import org.flowable.bpmn.model.SequenceFlow;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;import java.awt.*;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;/*** @description:* @author:wanzh* @date:2022/1/6*/
@Slf4j
@Service
public class ActInstanceServiceImpl implements ActInstanceService {@Autowiredprivate RuntimeService runtimeService;@Autowiredprivate RepositoryService repositoryService;@Autowiredprivate HistoryService historyService;@Autowiredprivate SpringProcessEngineConfiguration processEngineConfiguration;@Autowiredprivate ActCommonMapper actCommonMapper;private static final String ACT_TYPE_GATEWAY = "Gateway";private static final String ACT_TYPE_START = "startEvent";private static final String ACT_TYPE_END = "endEvent";private static final String ACTION_ADD_COMMENT = "AddComment";/**<select id="findInstance" resultType="com.bho.flowable.dto.ProcessInstanceResp">select hp.id_ as id,hp.name_ as name,p.key_ as processDefinitionKey,p.name_ as processDefinitionName,t.suspension_state_ as suspensionState,hp.duration_ as duration,hp.start_time_ as startTime,hp.rev_ as revision,hp.business_key_ as businessKey,t.name_ as nodeNamefrom act_hi_procinst hp left join act_re_procdef p  on hp.proc_def_id_ = p.id_left join act_ru_task t  on t.proc_inst_id_ = hp.id_</select>*/@Overridepublic PageInfo findObjectByPage(ReqFormParam reqFormParam) {PageHelper.startPage(reqFormParam.getPageNum(), reqFormParam.getPageSize()," startTime desc"); // , trueList<ProcessInstanceResp> instances = actCommonMapper.findInstance(reqFormParam.getTitle(),reqFormParam.getName(),reqFormParam.getKey());for(ProcessInstanceResp resp : instances){Long duration = resp.getDuration();resp.setDurationStr(CommonUtils.getDuration(duration));}return new PageInfo(instances);}@Overridepublic void delete(String key) {runtimeService.deleteProcessInstance(key,"删除流程实例");}@Overridepublic void deleteHistory(String id) {historyService.deleteHistoricProcessInstance(id);}@Overridepublic void activate(String id) {ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult();if(processInstance == null){throw new BaseException("当前流程实例不存在");}if(processInstance.isSuspended()){runtimeService.activateProcessInstanceById(id);}else {throw new BaseException("当前流程实例已激活,请不要重复操作");}}@Overridepublic void suspend(String id) {ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult();if(processInstance == null){throw new BaseException("当前流程实例不存在");}if(!processInstance.isSuspended()){runtimeService.suspendProcessInstanceById(id);}else {throw new BaseException("当前流程实例已挂起,请不要重复操作");}}//当前接口会签或者并行网关未验证可能存在问题,这里未涉及到这些所以只是简单的写了一下。@Overridepublic void viewImage(OutputStream out, String id) {try {HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(id).singleResult();if(processInstance == null){throw new BaseException("当前流程实例不存在");}//根据流程定义ID获取BpmnModelString processDefinitionId = processInstance.getProcessDefinitionId();BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);//得到已经走过的节点的id集合List<String> highLightedRunActivitis = new ArrayList<>();if(processInstance.getEndTime() == null){String executionId=processInstance.getId();highLightedRunActivitis = runtimeService.getActiveActivityIds(executionId);}List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery().processInstanceId(id).orderByHistoricActivityInstanceStartTime().asc().list();List<String> highLightedActivitis = new ArrayList<>();for(HistoricActivityInstance hi:historicActivityInstanceList) {highLightedActivitis.add(hi.getActivityId());}//得到走过的流的id集合List<String> highLightedFlows = new ArrayList<>();for (int i = 0; i < historicActivityInstanceList.size(); i++) {HistoricActivityInstance currentActivityInstance = historicActivityInstanceList.get(i);FlowNode currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivityInstance.getActivityId(), true);List<SequenceFlow> sequenceFlows = currentFlowNode.getOutgoingFlows();if(i < historicActivityInstanceList.size() - 1){HistoricActivityInstance nextActivityInstance = historicActivityInstanceList.get(i + 1);for (SequenceFlow sequenceFlow : sequenceFlows) {if (nextActivityInstance.getActivityId().equals(sequenceFlow.getTargetRef())) {highLightedFlows.add(sequenceFlow.getId());}}}}//获取输入流以及高亮显示CustomProcessDiagramGenerator customProcessDiagramGenerator = new CustomProcessDiagramGenerator();InputStream in = customProcessDiagramGenerator.generateDiagram(bpmnModel, "png", highLightedActivitis,highLightedFlows,processEngineConfiguration.getActivityFontName(),processEngineConfiguration.getLabelFontName(),//防止中文乱码processEngineConfiguration.getAnnotationFontName(),processEngineConfiguration.getClassLoader(),1.0 , new Color[] { FlowableConstants.COLOR_NORMAL, FlowableConstants.COLOR_CURRENT },new HashSet<>(highLightedRunActivitis));//输出图片到指定路径IOUtils.copy(in, out);}catch (Exception e){log.error("获取流程图出错:{}",e);throw new BaseException("获取流程图出错");}}/**<select id="findApprovalHistory" resultType="com.bho.flowable.dto.ApprovalHistoryResp">select ha.act_name_ as actName,ha.act_type_ as actType,ha.start_time_ as startTime,ha.duration_ as duration,hc.user_id_ as userId,hc.message_ as message,hc.action_ as actionfrom act_hi_actinst ha left join act_hi_comment hc on ha.task_id_ = hc.task_id_where ha.proc_inst_id_ = #{id}order by ha.start_time_,ha.act_type_</select>*/@Overridepublic List findApprovalHistory(String id) {List<ApprovalHistoryResp> approvalHistoryResps = actCommonMapper.findApprovalHistory(id);List<ApprovalHistoryResp> returnList = new ArrayList<>();String userId = actCommonMapper.findStartUser(id);for(int i = 0; i < approvalHistoryResps.size(); i++){ApprovalHistoryResp resp = approvalHistoryResps.get(i);if(resp.getActType().contains(ACT_TYPE_GATEWAY)){continue;}if(!StringUtils.isEmpty(resp.getAction()) && !resp.getAction().equals(ACTION_ADD_COMMENT)){continue;}if(resp.getActType().equals(ACT_TYPE_START)){resp.setOpTypeName(OpTypeEnum.COMMIT.getName());resp.setUserId(userId);resp.setActName("开始节点");resp.setMessage("开始节点跳过");}else if(resp.getActType().equals(ACT_TYPE_END)){resp.setOpTypeName(OpTypeEnum.COMMIT.getName());resp.setActName("结束节点");resp.setMessage("结束节点跳过");resp.setUserId(userId);}else{if(StringUtils.isEmpty(resp.getMessage())){if(i < approvalHistoryResps.size() - 1){resp.setOpTypeName(OpTypeEnum.COMMIT.getName());resp.setMessage("用户任务跳过");resp.setUserId(userId);}}else{String message = resp.getMessage();int index = message.indexOf(CommonConst.UNDER_LINE_STR);String opTypeKey = message.substring(0, index);OpTypeEnum opType = OpTypeEnum.getOpType(opTypeKey);resp.setOpTypeName(opType == null ? "未知" : opType.getName());resp.setMessage(message.substring(index + 1));}}Long duration = resp.getDuration();resp.setDurationStr(CommonUtils.getDuration(duration));returnList.add(resp);userId = resp.getUserId();}return returnList;}
}

为了区分流程当前审批节点以及已审批过的节点,调整流程图样式,后面截图会看到效果

import org.flowable.bpmn.model.BpmnModel;
import org.flowable.image.ProcessDiagramGenerator;import java.awt.*;
import java.io.InputStream;
import java.util.List;
import java.util.Set;public interface ICustomProcessDiagramGenerator extends ProcessDiagramGenerator {InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List<String> highLightedActivities,List<String> highLightedFlows, String activityFontName, String labelFontName, String annotationFontName,ClassLoader customClassLoader, double scaleFactor, Color[] colors, Set<String> currIds);
}

import com.bho.flowable.utils.FlowableConstants;
import org.flowable.bpmn.model.AssociationDirection;
import org.flowable.bpmn.model.GraphicInfo;
import org.flowable.image.exception.FlowableImageException;
import org.flowable.image.impl.DefaultProcessDiagramCanvas;
import org.flowable.image.util.ReflectUtil;import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;public class CustomProcessDiagramCanvas extends DefaultProcessDiagramCanvas {protected static Color LABEL_COLOR = new Color(0, 0, 0);//fontprotected String activityFontName = "宋体";protected String labelFontName = "宋体";protected String annotationFontName = "宋体";private static volatile boolean flag = false;public CustomProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType) {super(width, height, minX, minY, imageType);}public CustomProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType,String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader) {super(width, height, minX, minY, imageType, activityFontName, labelFontName, annotationFontName,customClassLoader);}public void drawHighLight(boolean isStartOrEnd,boolean isGateway, int x, int y, int width, int height, Color color) {Paint originalPaint = g.getPaint();Stroke originalStroke = g.getStroke();g.setPaint(color);g.setStroke(MULTI_INSTANCE_STROKE);if (isStartOrEnd) {// 开始、结束节点画圆g.drawOval(x, y, width, height);}else if(isGateway){int px[] = { x, x + width / 2, x + width, x + width / 2};int py[] = { y + height / 2, y + height,  y + height / 2, y};g.drawPolygon(px, py, px.length);}else {// 非开始、结束节点画圆角矩形RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 5, 5);g.draw(rect);}g.setPaint(originalPaint);g.setStroke(originalStroke);}public void drawSequenceflow(int[] xPoints, int[] yPoints, boolean conditional, boolean isDefault,boolean highLighted, double scaleFactor, Color color) {drawConnection(xPoints, yPoints, conditional, isDefault, "sequenceFlow", AssociationDirection.ONE, highLighted,scaleFactor, color);}public void drawConnection(int[] xPoints, int[] yPoints, boolean conditional, boolean isDefault,String connectionType, AssociationDirection associationDirection, boolean highLighted, double scaleFactor,Color color) {Paint originalPaint = g.getPaint();Stroke originalStroke = g.getStroke();g.setPaint(CONNECTION_COLOR);if (connectionType.equals("association")) {g.setStroke(ASSOCIATION_STROKE);} else if (highLighted) {g.setPaint(color);g.setStroke(HIGHLIGHT_FLOW_STROKE);}for (int i = 1; i < xPoints.length; i++) {Integer sourceX = xPoints[i - 1];Integer sourceY = yPoints[i - 1];Integer targetX = xPoints[i];Integer targetY = yPoints[i];Line2D.Double line = new Line2D.Double(sourceX, sourceY, targetX, targetY);g.draw(line);}if (isDefault) {Line2D.Double line = new Line2D.Double(xPoints[0], yPoints[0], xPoints[1], yPoints[1]);drawDefaultSequenceFlowIndicator(line, scaleFactor);}if (conditional) {Line2D.Double line = new Line2D.Double(xPoints[0], yPoints[0], xPoints[1], yPoints[1]);drawConditionalSequenceFlowIndicator(line, scaleFactor);}if (associationDirection.equals(AssociationDirection.ONE)|| associationDirection.equals(AssociationDirection.BOTH)) {Line2D.Double line = new Line2D.Double(xPoints[xPoints.length - 2], yPoints[xPoints.length - 2],xPoints[xPoints.length - 1], yPoints[xPoints.length - 1]);drawArrowHead(line, scaleFactor);}if (associationDirection.equals(AssociationDirection.BOTH)) {Line2D.Double line = new Line2D.Double(xPoints[1], yPoints[1], xPoints[0], yPoints[0]);drawArrowHead(line, scaleFactor);}g.setPaint(originalPaint);g.setStroke(originalStroke);}public void drawLabel(boolean highLighted, String text, GraphicInfo graphicInfo, boolean centered) {float interline = 1.0f;// textif (text != null && text.length() > 0) {Paint originalPaint = g.getPaint();Font originalFont = g.getFont();if (highLighted) {g.setPaint(FlowableConstants.COLOR_NORMAL);} else {g.setPaint(LABEL_COLOR);}g.setFont(new Font(labelFontName, Font.BOLD, 10));int wrapWidth = 100;int textY = (int) graphicInfo.getY();// TODO: use drawMultilineText()AttributedString as = new AttributedString(text);as.addAttribute(TextAttribute.FOREGROUND, g.getPaint());as.addAttribute(TextAttribute.FONT, g.getFont());AttributedCharacterIterator aci = as.getIterator();FontRenderContext frc = new FontRenderContext(null, true, false);LineBreakMeasurer lbm = new LineBreakMeasurer(aci, frc);while (lbm.getPosition() < text.length()) {TextLayout tl = lbm.nextLayout(wrapWidth);textY += tl.getAscent();Rectangle2D bb = tl.getBounds();double tX = graphicInfo.getX();if (centered) {tX += (int) (graphicInfo.getWidth() / 2 - bb.getWidth() / 2);}tl.draw(g, (float) tX, textY);textY += tl.getDescent() + tl.getLeading() + (interline - 1.0f) * tl.getAscent();}// restore originalsg.setFont(originalFont);g.setPaint(originalPaint);}}@Overridepublic BufferedImage generateBufferedImage(String imageType) {if (closed) {throw new FlowableImageException("ProcessDiagramGenerator already closed");}// Try to remove white spaceminX = (minX <= FlowableConstants.PROCESS_PADDING) ? FlowableConstants.PROCESS_PADDING : minX;minY = (minY <= FlowableConstants.PROCESS_PADDING) ? FlowableConstants.PROCESS_PADDING : minY;BufferedImage imageToSerialize = processDiagram;if (minX >= 0 && minY >= 0) {imageToSerialize = processDiagram.getSubimage(minX - FlowableConstants.PROCESS_PADDING,minY - FlowableConstants.PROCESS_PADDING,canvasWidth - minX + FlowableConstants.PROCESS_PADDING,canvasHeight - minY + FlowableConstants.PROCESS_PADDING);}return imageToSerialize;}@Overridepublic void initialize(String imageType) {activityFontName = "宋体";labelFontName = "宋体";this.processDiagram = new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_ARGB);this.g = processDiagram.createGraphics();g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);g.setPaint(Color.black);Font font = new Font(activityFontName, Font.BOLD, FONT_SIZE);g.setFont(font);this.fontMetrics = g.getFontMetrics();LABEL_FONT = new Font(labelFontName, Font.ITALIC, 10);ANNOTATION_FONT = new Font(annotationFontName, Font.PLAIN, FONT_SIZE);//优化加载速度if(flag) {return;}try {USERTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/userTask.png", customClassLoader));SCRIPTTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/scriptTask.png", customClassLoader));SERVICETASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/serviceTask.png", customClassLoader));RECEIVETASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/receiveTask.png", customClassLoader));SENDTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/sendTask.png", customClassLoader));MANUALTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/manualTask.png", customClassLoader));BUSINESS_RULE_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/businessRuleTask.png", customClassLoader));SHELL_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/shellTask.png", customClassLoader));CAMEL_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/camelTask.png", customClassLoader));MULE_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/muleTask.png", customClassLoader));TIMER_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/timer.png", customClassLoader));COMPENSATE_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/compensate-throw.png", customClassLoader));COMPENSATE_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/compensate.png", customClassLoader));ERROR_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/error-throw.png", customClassLoader));ERROR_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/error.png", customClassLoader));MESSAGE_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/message-throw.png", customClassLoader));MESSAGE_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/message.png", customClassLoader));SIGNAL_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/signal-throw.png", customClassLoader));SIGNAL_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/signal.png", customClassLoader));
/*        String baseUrl = Thread.currentThread().getContextClassLoader().getResource("static/img/flowable/").getPath();SCRIPTTASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"scriptTask.png"));USERTASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"userTask.png"));SERVICETASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"serviceTask.png"));RECEIVETASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"receiveTask.png"));SENDTASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"sendTask.png"));MANUALTASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"manualTask.png"));BUSINESS_RULE_TASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"businessRuleTask.png"));SHELL_TASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"shellTask.png"));CAMEL_TASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"camelTask.png"));MULE_TASK_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"muleTask.png"));TIMER_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"timer.png"));COMPENSATE_THROW_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"compensate-throw.png"));COMPENSATE_CATCH_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"compensate.png"));ERROR_THROW_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"error-throw.png"));ERROR_CATCH_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"error.png"));MESSAGE_THROW_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"message-throw.png"));MESSAGE_CATCH_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"message.png"));SIGNAL_THROW_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"signal-throw.png"));SIGNAL_CATCH_IMAGE = ImageIO.read(new FileInputStream(baseUrl+"signal.png"));*/flag = true;} catch (IOException e) {flag = false;LOGGER.warn("Could not load image for process diagram creation: {}", e.getMessage());}}}

5.3.2 前端页面

发起流程

调用flowable提供的接口可查看审批历史和流程图,这个页面只提供查询操作,审批流程在待办里面处理,处理完毕回调接口更新状态。



5.4 流程审批

我的待办 我的已办 办理

5.4.1 后端代码


import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.bho.flowable.dto.*;
import com.bho.flowable.form.ReqFormParam;
import com.bho.flowable.mapper.ActCommonMapper;
import com.bho.flowable.service.ActTaskService;
import com.bho.model.BaseException;
import com.bho.model.RestResponse;
import com.bho.user.feign.SysUserApi;
import com.bho.utils.*;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.extern.slf4j.Slf4j;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.engine.HistoryService;
import org.flowable.engine.TaskService;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.identitylink.api.IdentityLink;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** @description:* @author:wanzh* @date:2022/1/7*/
@Slf4j
@Service
public class ActTaskServiceImpl implements ActTaskService {@Autowiredprivate ActCommonMapper actCommonMapper;@Autowiredprivate SysUserApi sysUserApi;@Autowiredprivate TaskService taskService;@Autowiredprivate HistoryService historyService;@Overridepublic PageInfo findToDo(ReqFormParam reqFormParam) {Integer userId = SysUserUtils.getCurrentLoginUser().getId();RestResponse<List<Integer>> restResponse = sysUserApi.findRolesByUserId(userId);if(restResponse.getCode() != RestResponse.SUCCESS_CODE){throw new BaseException("查询角色接口出错");}List<Integer> roleIdList = restResponse.getData();PageHelper.startPage(reqFormParam.getPageNum(), reqFormParam.getPageSize()," createTime desc"); // , trueList<MyTaskResp> tasks = actCommonMapper.findToDoTask(reqFormParam.getTitle(),reqFormParam.getName(),reqFormParam.getKey(),userId,roleIdList);return new PageInfo(tasks);}@Overridepublic PageInfo findDone(ReqFormParam reqFormParam) {Integer userId = SysUserUtils.getCurrentLoginUser().getId();RestResponse<List<Integer>> restResponse = sysUserApi.findRolesByUserId(userId);if(restResponse.getCode() != RestResponse.SUCCESS_CODE){throw new BaseException("查询角色接口出错");}List<Integer> roleIdList = restResponse.getData();PageHelper.startPage(reqFormParam.getPageNum(), reqFormParam.getPageSize()," createTime desc"); // , trueList<MyTaskResp> tasks = actCommonMapper.findDone(reqFormParam.getTitle(),reqFormParam.getName(),reqFormParam.getKey(),userId,roleIdList);for(MyTaskResp resp : tasks){Long duration = resp.getDuration();resp.setDurationStr(CommonUtils.getDuration(duration));}return new PageInfo(tasks);}@Overridepublic void complete(CompleteTaskReq completeTaskReq) {OpTypeEnum opType = OpTypeEnum.getOpType(completeTaskReq.getApprovalOpType());if(opType == null){throw new BaseException("未知的审批操作类型");}Integer userId = SysUserUtils.getCurrentLoginUser().getId();Authentication.setAuthenticatedUserId(String.valueOf(userId));completeTaskReq.setMessage(completeTaskReq.getApprovalOpType() + CommonConst.UNDER_LINE_STR + completeTaskReq.getMessage());taskService.addComment(completeTaskReq.getTaskId(), completeTaskReq.getInstanceId(), completeTaskReq.getMessage());//comment为批注内容// 添加批注信息if(opType == OpTypeEnum.TRANSFER){if(StringUtils.isEmpty(completeTaskReq.getUserId())){Authentication.setAuthenticatedUserId(null);throw new BaseException("转办用户不能为空");}List<IdentityLink> identityLinksForTask = taskService.getIdentityLinksForTask(completeTaskReq.getTaskId());for(IdentityLink identityLink : identityLinksForTask){if(!StringUtils.isEmpty(identityLink.getUserId())){taskService.deleteUserIdentityLink(completeTaskReq.getTaskId(), identityLink.getUserId(), identityLink.getType());}if(!StringUtils.isEmpty(identityLink.getGroupId())){taskService.deleteUserIdentityLink(completeTaskReq.getTaskId(),identityLink.getGroupId(), identityLink.getType());}}taskService.setAssignee(completeTaskReq.getTaskId(), completeTaskReq.getUserId());if(!StringUtils.isEmpty(completeTaskReq.getTransferUserFieldName())){taskService.setVariable(completeTaskReq.getTaskId(),completeTaskReq.getTransferUserFieldName(),completeTaskReq.getUserId());Map<String, Object> variables = new HashMap<>();variables.put("approvalOpType",completeTaskReq.getApprovalOpType());variables.put(completeTaskReq.getTransferUserFieldName(),completeTaskReq.getUserId());HistoricProcessInstance hpi = historyService.createHistoricProcessInstanceQuery().processInstanceId(completeTaskReq.getInstanceId()).singleResult();if(hpi != null && !StringUtils.isEmpty(hpi.getCallbackId()) && !StringUtils.isEmpty(hpi.getCallbackType())){HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();variables.put("id",hpi.getBusinessKey());if(hpi.getCallbackType().equals(CommonConst.METHOD_TYPE_POST)){String result = HttpClientUtils.httpPostJsonAndHeader(hpi.getCallbackId(), JacksonUtil.beanToJson(variables), CommonConst.REQ_HEADER_AUTH, request.getHeader(CommonConst.REQ_HEADER_AUTH));log.debug("result:{}",result);}else{String result = HttpClientUtils.httpPutJsonAndHeader(hpi.getCallbackId(),JacksonUtil.beanToJson(variables),CommonConst.REQ_HEADER_AUTH,request.getHeader(CommonConst.REQ_HEADER_AUTH));log.debug("result:{}",result);}}}}else{Map<String, Object> variables = JacksonUtil.jsonToMap(completeTaskReq.getFormData(), String.class, Object.class);variables.put("approvalOpType",completeTaskReq.getApprovalOpType());taskService.complete(completeTaskReq.getTaskId(),variables);HistoricProcessInstance hpi = historyService.createHistoricProcessInstanceQuery().processInstanceId(completeTaskReq.getInstanceId()).singleResult();if(hpi != null && !StringUtils.isEmpty(hpi.getCallbackId()) && !StringUtils.isEmpty(hpi.getCallbackType())){HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();variables.put("id",hpi.getBusinessKey());if(hpi.getCallbackType().equals(CommonConst.METHOD_TYPE_POST)){String result = HttpClientUtils.httpPostJsonAndHeader(hpi.getCallbackId(), JacksonUtil.beanToJson(variables), CommonConst.REQ_HEADER_AUTH, request.getHeader(CommonConst.REQ_HEADER_AUTH));log.info("result:{}",result);}else{String result = HttpClientUtils.httpPutJsonAndHeader(hpi.getCallbackId(),JacksonUtil.beanToJson(variables),CommonConst.REQ_HEADER_AUTH,request.getHeader(CommonConst.REQ_HEADER_AUTH));log.info("result:{}",result);}}}Authentication.setAuthenticatedUserId(null);}@Overridepublic TaskFormResp findForm(String id) {TaskFormResp taskFormResp =  actCommonMapper.findFormByTaskId(id);if(taskFormResp == null){throw new BaseException("任务不存在");}String modelEditorJson = taskFormResp.getModelEditorJson();JSONObject jsonObject =  JSONObject.parseObject(modelEditorJson);JSONArray childShapes = jsonObject.getJSONArray("childShapes");for(int i = 0; i < childShapes.size(); i++){JSONObject childShape = childShapes.getJSONObject(i);String resourceId = childShape.getString("resourceId");JSONObject properties = childShape.getJSONObject("properties");String overrideid = properties.getString("overrideid");if(!StringUtils.isEmpty(overrideid)){resourceId = overrideid;}if(resourceId.equals(taskFormResp.getTaskDefKey())){JSONArray formProperties = new JSONArray();JSONObject formproperties = properties.getJSONObject("formproperties");if(formproperties != null){JSONArray tempFormProperties = formproperties.getJSONArray("formProperties");if(tempFormProperties != null){formProperties = tempFormProperties;}}taskFormResp.setModelEditorJson(null);taskFormResp.setFormJson(formProperties.toJSONString());}}List<OpTypeDto> opTypeList = new ArrayList<>();String[] split = taskFormResp.getTaskDefKey().split(CommonConst.UNDER_LINE_STR);for(int i = 1; i < split.length; i++){String key = split[i];OpTypeEnum opType = OpTypeEnum.getOpType(key);if(opType != null){opTypeList.add(OpTypeDto.builder().key(opType.getKey()).name(opType.getName()).build());}}taskFormResp.setOpTypeList(opTypeList);return taskFormResp;}}

5.4.2 前端页面

待办页面点击办理按钮

为了方便派单还是选择自己







办理页面动态显示表单

6.完结

对于flowable整合到项目里,我这里只是简单整合一下,能满足当前业务场景就行,没有做深入研究,基本都是百度搜索一点点凑起来的,若需要源码可私信,只发送flowable服务代码(会用到其它服务一些共用文件配置,其它服务代码不提供)。若遇到问题也可以共用探讨,一起进步。

Flowable入门相关推荐

  1. Flowable 快速入门教程:Flowable 入门开发案例,结合流程设计器详细讲解

    Flowable 快速入门教程:Flowable 入门开发案例,结合流程设计器详细讲解 前言 流程设计器集成 整体流程图 流程节点说明 第一审核人节点:实际设置审核人 配置信息 说明 第二审核人:参数 ...

  2. Flowable入门指引

    Flowable入门指引 一.基本概念* 二.Flowable核心数据库表 三.集成绘制工作流插件 四.7大Service介绍 五.代码演示 六.总结与优化 一.基本概念* 工作流的重中之重就是先了解 ...

  3. 【flowable】二、flowable入门

    flowable入门  在这个初步教程中,将构建一个简单的例子,以展示如何创建一个Flowable流程引擎,介绍一些核心概念,并展示如何使用API. 截图时使用的是Eclipse,但实际上可以使用任何 ...

  4. flowable画图教程_JeeGit企业级快速开发平台-JeeSite4 Flowable入门教程

    注: 998元以下课程无咨询服务 该课程包含服务内容:299元含发票 在线课程观看权 购课后,教学资源联系长春叭姐QQ:3211247533 索要 教学内容 3.1 第一章 业务流程 BPM.工作流引 ...

  5. Flowable入门系列文章47 - 电子邮件任务

    Flowable允许您通过向一个或多个收件人发送电子邮件的自动邮件服务任务来增强业务流程,包括支持cc,bcc,HTML内容等.请注意,邮件任务不是 BPMN 2.0规范的正式任务(因此没有专门的图标 ...

  6. Flowable入门系列文章195 - JMX的组态和服务URL

    1.组态 JMX使用默认配置,以便于使用最常用的配置进行部署.但是,更改默认配置很容易.您可以通过编程或通过配置文件来完成.下面的代码片段显示了如何在配置文件中完成这个工作: <bean id= ...

  7. Flowable入门系列文章11 - Flowable API 01

    1.Process Engine API和服务 引擎API是与Flowable进行交互的最常见的方式.主要的出发点是ProcessEngine,可以按照配置部分所述的几种方式创建 .从 Process ...

  8. Flowable入门系列文章35 - Activity解读 11

    1.消息中间捕捉事件 描述 中间捕获 消息事件捕获具有指定名称的消息. 图形表示法 中间捕捉消息事件可视化为一个典型的中间事件(圆圈内有较小的圆圈),里面有消息图标.消息图标是白色(未填充),以指示其 ...

  9. Flowable入门系列文章194 - JMX的基本介绍和属性说明

    1.介绍 可以使用标准的Java管理扩展(JMX)技术连接到Flowable引擎,以获取信息或更改其行为.任何标准的JMX客户端都可以用于这个目的.启用和禁用Job Executor,部署新的流程定义 ...

最新文章

  1. 原来这才是游戏上瘾的机制 如果把它用到生活中的话
  2. 面向对象分析和设计的几个关键步骤_(豁然开朗)《面向对象分析与设计》读书笔记 (4)- 分类...
  3. WINDOWS渗透与提权总结(1)
  4. 利用规则引擎计算个人所得税学习
  5. C/C++ 位操作 总结
  6. equipment download scenario3
  7. java version 和javac版本不一致_解决linux下javac -version和java -version版本显示不一致...
  8. 初识斯蒂尔杰斯积分(Stieltjes integral)
  9. 【opencv学习】【形态学】【腐蚀与膨胀】【开运算与闭运算】【礼帽和黑帽】
  10. 2014.7.8模拟赛【笨笨当粉刷匠】|bzoj1296 [SCOI]粉刷匠
  11. nginx SSL证书配置(双向认证)
  12. java 修改字体大小
  13. PCBA加工组装需要的设备有哪些呢?
  14. html5 浮标,【钓鱼技巧】主说调与钓 附5个实战技巧
  15. mysql minus 语句用法,mysql如何用minus运算符?
  16. Geek Challenge
  17. 最好用的临时邮箱网站
  18. 视频工厂:如何拍摄优质的餐饮宣传片之品牌植入篇
  19. STM32F4寄存器初始化系列:GPIO
  20. illumina 二代测序原理及过程

热门文章

  1. 基于IXP2800处理器的VPLS转发设计与实现
  2. 推模式下如何保证消息不丢失?
  3. java怎么循环_java怎么实现循环
  4. 谷歌浏览器将网络设置为3G 或者更慢的测试网络
  5. java集成r语言_R语言- 实验报告 - 利用R语言脚本与Java相互调用
  6. TC8:TCP_UNACCEPTABLE_01-04
  7. 修改手机IP地址方法
  8. yh的生日,我的策划
  9. 京东价格监控软件开发技术探讨十:如何获取浏览器指纹
  10. Android 获取当前时间