Flowable入门

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);

    @Bean
    public SessionLocaleResolver localeResolver() {
        return new SessionLocaleResolver();
    }

    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        LOGGER.debug("Configuring localeChangeInterceptor");
        LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
        localeChangeInterceptor.setParamName("language");
        return localeChangeInterceptor;
    }

    @Override
    public 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 {

    @Bean
    public 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_";


	@Bean
	public 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);

    @Autowired
    protected 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 {


 @Autowired
 private SysUserApi sysUserApi;

 @Autowired
 private 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";

    @Autowired
    private ModelService modelService;

    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private ActCommonMapper actCommonMapper;


    @Override
    @Transactional
    public 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

    }

    @Override
    public int delete(String key) {
        modelService.deleteModel(key);
        return 0;
    }

    @Override
    public 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>
	*/
    @Override
    public PageInfo findObjectByPage(ReqFormParam reqFormParam) {
        PageHelper.startPage(reqFormParam.getPageNum(), reqFormParam.getPageSize()); // , true
        List<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 {

    @Autowired
    private RepositoryService repositoryService;

    @Override
    public 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("当前流程已被激活,请不要重复操作");
        }
    }

    @Override
    public 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("当前流程已被挂起,请不要重复操作");
        }
    }

    @Override
    public 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("获取流程图出错");
        }
    }

    @Override
    public int delete(String key) {
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(key).singleResult();
        if(processDefinition == null){
            throw new BaseException("当前流程不存在");
        }
        repositoryService.deleteDeployment(processDefinition.getDeploymentId(), true);
        return 0;
    }

    @Override
    public 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> {


    @Override
    public 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{

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private RepositoryService repositoryService;


    @Override
    public 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 {


    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private HistoryService historyService;

    @Autowired
    private SpringProcessEngineConfiguration processEngineConfiguration;

    @Autowired
    private 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 nodeName
        from 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>
	*/

    @Override
    public PageInfo findObjectByPage(ReqFormParam reqFormParam) {
        PageHelper.startPage(reqFormParam.getPageNum(), reqFormParam.getPageSize()," startTime desc"); // , true
        List<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);
    }

    @Override
    public void delete(String key) {
        runtimeService.deleteProcessInstance(key,"删除流程实例");
    }

    @Override
    public void deleteHistory(String id) {
        historyService.deleteHistoricProcessInstance(id);
    }


    @Override
    public 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("当前流程实例已激活,请不要重复操作");
        }

    }

    @Override
    public 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("当前流程实例已挂起,请不要重复操作");
        }
    }

	//当前接口会签或者并行网关未验证可能存在问题,这里未涉及到这些所以只是简单的写了一下。
    @Override
    public void viewImage(OutputStream out, String id) {
        try {
            HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(id).singleResult();
            if(processInstance == null){
                throw new BaseException("当前流程实例不存在");
            }
            //根据流程定义ID获取BpmnModel
            String 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 action
        from 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>
	*/
    @Override
    public 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);

    //font
    protected 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;

        // text
        if (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 originals
            g.setFont(originalFont);
            g.setPaint(originalPaint);
        }
    }

    @Override
    public BufferedImage generateBufferedImage(String imageType) {
        if (closed) {
            throw new FlowableImageException("ProcessDiagramGenerator already closed");
        }

        // Try to remove white space
        minX = (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;
    }

    @Override
    public 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 {

    @Autowired
    private ActCommonMapper actCommonMapper;

    @Autowired
    private SysUserApi sysUserApi;

    @Autowired
    private TaskService taskService;

    @Autowired
    private HistoryService historyService;


    @Override
    public 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"); // , true
        List<MyTaskResp> tasks = actCommonMapper.findToDoTask(reqFormParam.getTitle(),reqFormParam.getName(),reqFormParam.getKey(),userId,roleIdList);
        return new PageInfo(tasks);
    }

    @Override
    public 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"); // , true
        List<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);
    }

    @Override
    public 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);
    }

    @Override
    public 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服务代码(会用到其它服务一些共用文件配置,其它服务代码不提供)。若遇到问题也可以共用探讨,一起进步。
在这里插入图片描述

  • 6
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Flowable是一个开源的工作流引擎,用于设计、执行和管理工作流程。以下是Flowable入门的一些建议步骤: 1. 安装Flowable:你可以通过Maven或Gradle将Flowable引入到你的项目中。在pom.xml(或build.gradle)文件中添加Flowable的依赖项。 2. 创建Flowable引擎:在你的应用程序中,使用Flowable API创建一个Flowable引擎实例。这将是与Flowable进行交互的入口点。 3. 定义流程模型:使用Flowable的BPMN 2.0规范来设计和定义你的流程模型。你可以使用可视化工具(如Flowable Modeler)或直接编写BPMN XML文件。 4. 部署流程模型:将你的流程模型部署到Flowable引擎中,以便可以执行和管理这些流程。可以使用Flowable API来完成部署。 5. 执行流程实例:通过启动流程实例来执行你的流程模型。你可以使用Flowable API来启动流程实例,并跟踪流程的执行状态。 6. 处理任务:在流程实例中,任务是需要人工干预或处理的环节。你可以通过Flowable API查询和完成这些任务。 7. 监控和管理流程:Flowable提供了一套管理和监控工具,用于跟踪流程实例、任务和其他相关数据。你可以使用这些工具来监控流程的执行和性能。 这只是一个简单的Flowable入门指南,帮助你了解基本的概念和步骤。Flowable还提供了更多高级功能,如事件、表单、决策表等,你可以根据自己的需求进行深入学习和实践。建议你参考Flowable的官方文档和示例代码,以获得更详细的指导和帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值