工作流模块提交

This commit is contained in:
2024-05-09 14:23:18 +08:00
parent bedd70136b
commit 4936d22884
98 changed files with 6780 additions and 108 deletions

View File

@@ -0,0 +1,151 @@
package com.njcn.bpm.utils;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.bpm.enums.BpmModelFormTypeEnum;
import com.njcn.bpm.pojo.dto.BpmModelMetaInfoRespDTO;
import com.njcn.bpm.pojo.dto.PageResult;
import com.njcn.bpm.pojo.param.BpmModelParam;
import com.njcn.bpm.pojo.po.BpmCategory;
import com.njcn.bpm.pojo.po.BpmForm;
import com.njcn.bpm.pojo.po.BpmProcessDefinitionInfo;
import com.njcn.bpm.pojo.vo.BpmModelRespVO;
import com.njcn.bpm.pojo.vo.BpmProcessDefinitionInfoVO;
import org.flowable.common.engine.impl.db.SuspensionState;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ProcessDefinition;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* 流程模型 Convert
*
* @author yunlongn
*/
@Mapper
public interface BpmModelConvert {
BpmModelConvert INSTANCE = Mappers.getMapper(BpmModelConvert.class);
default Page<BpmModelRespVO> buildModelPage(Page<Model> pageResult,
Map<String, BpmForm> formMap,
Map<String, BpmCategory> categoryMap, Map<String, Deployment> deploymentMap,
Map<String, ProcessDefinition> processDefinitionMap) {
List<BpmModelRespVO> list = CollectionUtils.convertList(pageResult.getRecords(), model -> {
BpmModelMetaInfoRespDTO metaInfo = buildMetaInfo(model);
BpmForm form = metaInfo != null ? formMap.get(metaInfo.getFormId()) : null;
BpmCategory category = categoryMap.get(model.getCategory());
Deployment deployment = model.getDeploymentId() != null ? deploymentMap.get(model.getDeploymentId()) : null;
ProcessDefinition processDefinition = model.getDeploymentId() != null ? processDefinitionMap.get(model.getDeploymentId()) : null;
return buildModel0(model, metaInfo, form, category, deployment, processDefinition);
});
Page<BpmModelRespVO> voPage = new Page<>();
voPage.setRecords(list);
voPage.setTotal(pageResult.getTotal());
voPage.setSize(pageResult.getSize());
voPage.setCurrent(pageResult.getCurrent());
return voPage;
}
default BpmModelRespVO buildModel(Model model,
byte[] bpmnBytes) {
BpmModelMetaInfoRespDTO metaInfo = buildMetaInfo(model);
BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null);
if (ArrayUtil.isNotEmpty(bpmnBytes)) {
modelVO.setBpmnXml(new String(bpmnBytes));
}
return modelVO;
}
default BpmModelRespVO buildModel0(Model model,
BpmModelMetaInfoRespDTO metaInfo, BpmForm form, BpmCategory category,
Deployment deployment, ProcessDefinition processDefinition) {
BpmModelRespVO modelRespVO = new BpmModelRespVO();
modelRespVO.setId(model.getId());
modelRespVO.setName(model.getName());
modelRespVO.setKey(model.getKey());
modelRespVO.setCategory(model.getCategory());
modelRespVO.setCreateTime(DateUtils.of(model.getCreateTime()));
// Form
if (metaInfo != null) {
modelRespVO.setFormType(metaInfo.getFormType());
modelRespVO.setFormId(metaInfo.getFormId());
modelRespVO.setFormCustomCreatePath(metaInfo.getFormCustomCreatePath());
modelRespVO.setFormCustomViewPath(metaInfo.getFormCustomViewPath());
modelRespVO.setIcon(metaInfo.getIcon());
modelRespVO.setDescription(metaInfo.getDescription());
}
if (form != null) {
modelRespVO.setFormId(form.getId());
modelRespVO.setFormName(form.getName());
}
// Category
if (category != null) {
modelRespVO.setCategoryName(category.getName());
}
// ProcessDefinition
if (processDefinition != null) {
modelRespVO.setProcessDefinition(BeanUtils.toBean(processDefinition, BpmProcessDefinitionInfoVO.class));
modelRespVO.getProcessDefinition().setSuspensionState(processDefinition.isSuspended() ?
SuspensionState.SUSPENDED.getStateCode() : SuspensionState.ACTIVE.getStateCode());
if (deployment != null) {
modelRespVO.getProcessDefinition().setDeploymentTime(DateUtils.of(deployment.getDeploymentTime()));
}
}
return modelRespVO;
}
default void copyToCreateModel(Model model, BpmModelParam bean) {
model.setName(bean.getName());
model.setKey(bean.getKey());
model.setCategory(bean.getCategory());
// model.setMetaInfo(buildMetaInfoStr(null,
// null, bean.getDescription(),
// null, null, null, null));
//类型暂时写死为表单 todo...
model.setMetaInfo(buildMetaInfoStr(buildMetaInfo(model),
bean.getIcon(), bean.getDescription(),
BpmModelFormTypeEnum.NORMAL.getType(), bean.getFormId(), bean.getFormCustomCreatePath(), bean.getFormCustomViewPath()));
}
default void copyToUpdateModel(Model model, BpmModelParam.BpmModelUpdateParam bean) {
model.setName(bean.getName());
model.setCategory(bean.getCategory());
model.setMetaInfo(buildMetaInfoStr(buildMetaInfo(model),
bean.getIcon(), bean.getDescription(),
bean.getFormType(), bean.getFormId(), bean.getFormCustomCreatePath(), bean.getFormCustomViewPath()));
}
default String buildMetaInfoStr(BpmModelMetaInfoRespDTO metaInfo,
String icon, String description,
Integer formType, String formId, String formCustomCreatePath, String formCustomViewPath) {
if (metaInfo == null) {
metaInfo = new BpmModelMetaInfoRespDTO();
}
// 只有非空,才进行设置,避免更新时的覆盖
if (StrUtil.isNotEmpty(icon)) {
metaInfo.setIcon(icon);
}
if (StrUtil.isNotEmpty(description)) {
metaInfo.setDescription(description);
}
if (Objects.nonNull(formType)) {
metaInfo.setFormType(formType);
metaInfo.setFormId(formId);
metaInfo.setFormCustomCreatePath(formCustomCreatePath);
metaInfo.setFormCustomViewPath(formCustomViewPath);
}
return JsonUtils.toJsonString(metaInfo);
}
default BpmModelMetaInfoRespDTO buildMetaInfo(Model model) {
return JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class);
}
}

View File

@@ -0,0 +1,100 @@
package com.njcn.bpm.utils;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.map.MapUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.bpm.pojo.po.BpmCategory;
import com.njcn.bpm.pojo.po.BpmForm;
import com.njcn.bpm.pojo.po.BpmProcessDefinitionInfo;
import com.njcn.bpm.pojo.vo.BpmProcessDefinitionInfoVO;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.UserTask;
import org.flowable.common.engine.impl.db.SuspensionState;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.factory.Mappers;
import java.util.List;
import java.util.Map;
/**
* Bpm 流程定义的 Convert
*
* @author yunlong.li
*/
@Mapper
public interface BpmProcessDefinitionConvert {
BpmProcessDefinitionConvert INSTANCE = Mappers.getMapper(BpmProcessDefinitionConvert.class);
default Page<BpmProcessDefinitionInfoVO> buildProcessDefinitionPage(Page<ProcessDefinition> page,
Map<String, Deployment> deploymentMap,
Map<String, BpmProcessDefinitionInfo> processDefinitionInfoMap,
Map<String, BpmForm> formMap,
Map<String, BpmCategory> categoryMap) {
List<BpmProcessDefinitionInfoVO> list = buildProcessDefinitionList(page.getRecords(), deploymentMap, processDefinitionInfoMap, formMap, categoryMap);
Page<BpmProcessDefinitionInfoVO> voPage = new Page<>();
voPage.setRecords(list);
voPage.setTotal(page.getTotal());
voPage.setSize(page.getSize());
voPage.setCurrent(page.getCurrent());
return voPage;
}
default List<BpmProcessDefinitionInfoVO> buildProcessDefinitionList(List<ProcessDefinition> list,
Map<String, Deployment> deploymentMap,
Map<String, BpmProcessDefinitionInfo> processDefinitionInfoMap,
Map<String, BpmForm> formMap,
Map<String, BpmCategory> categoryMap) {
return CollectionUtils.convertList(list, definition -> {
Deployment deployment = MapUtil.get(deploymentMap, definition.getDeploymentId(), Deployment.class);
BpmProcessDefinitionInfo processDefinitionInfo = MapUtil.get(processDefinitionInfoMap, definition.getId(), BpmProcessDefinitionInfo.class);
BpmForm form = null;
if (processDefinitionInfo != null) {
form = MapUtil.get(formMap, processDefinitionInfo.getFormId(), BpmForm.class);
}
BpmCategory category = MapUtil.get(categoryMap, definition.getCategory(), BpmCategory.class);
return buildProcessDefinition(definition, deployment, processDefinitionInfo, form, category, null, null);
});
}
default BpmProcessDefinitionInfoVO buildProcessDefinition(ProcessDefinition definition,
Deployment deployment,
BpmProcessDefinitionInfo processDefinitionInfo,
BpmForm form,
BpmCategory category,
BpmnModel bpmnModel,
List<UserTask> startUserSelectUserTaskList) {
BpmProcessDefinitionInfoVO respVO = BeanUtils.toBean(definition, BpmProcessDefinitionInfoVO.class);
respVO.setSuspensionState(definition.isSuspended() ? SuspensionState.SUSPENDED.getStateCode() : SuspensionState.ACTIVE.getStateCode());
// Deployment
if (deployment != null) {
respVO.setDeploymentTime(LocalDateTimeUtil.of(deployment.getDeploymentTime()));
}
// BpmProcessDefinitionInfoDO
if (processDefinitionInfo != null) {
copyTo(processDefinitionInfo, respVO);
// Form
if (form != null) {
respVO.setFormName(form.getName());
}
}
// Category
if (category != null) {
respVO.setCategoryName(category.getName());
}
// BpmnModel
if (bpmnModel != null) {
respVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnModel));
respVO.setStartUserSelectTasks(BeanUtils.toBean(startUserSelectUserTaskList, BpmProcessDefinitionInfoVO.UserTask.class));
}
return respVO;
}
@Mapping(source = "from.id", target = "to.id", ignore = true)
void copyTo(BpmProcessDefinitionInfo from, @MappingTarget BpmProcessDefinitionInfoVO to);
}

View File

@@ -0,0 +1,331 @@
package com.njcn.bpm.utils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import com.njcn.bpm.enums.BpmnModelConstants;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.common.engine.impl.util.io.BytesStreamSource;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 流程模型转操作工具类
*/
public class BpmnModelUtils {
public static Integer parseCandidateStrategy(FlowElement userTask) {
return NumberUtils.parseInt(userTask.getAttributeValue(
BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY));
}
public static String parseCandidateParam(FlowElement userTask) {
return userTask.getAttributeValue(
BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM);
}
/**
* 根据节点,获取入口连线
*
* @param source 起始节点
* @return 入口连线列表
*/
public static List<SequenceFlow> getElementIncomingFlows(FlowElement source) {
if (source instanceof FlowNode) {
return ((FlowNode) source).getIncomingFlows();
}
return new ArrayList<>();
}
/**
* 根据节点,获取出口连线
*
* @param source 起始节点
* @return 出口连线列表
*/
public static List<SequenceFlow> getElementOutgoingFlows(FlowElement source) {
if (source instanceof FlowNode) {
return ((FlowNode) source).getOutgoingFlows();
}
return new ArrayList<>();
}
/**
* 获取流程元素信息
*
* @param model bpmnModel 对象
* @param flowElementId 元素 ID
* @return 元素信息
*/
public static FlowElement getFlowElementById(BpmnModel model, String flowElementId) {
Process process = model.getMainProcess();
return process.getFlowElement(flowElementId);
}
/**
* 获得 BPMN 流程中,指定的元素们
*
* @param model 模型
* @param clazz 指定元素。例如说,{@link UserTask}、{@link Gateway} 等等
* @return 元素们
*/
public static <T extends FlowElement> List<T> getBpmnModelElements(BpmnModel model, Class<T> clazz) {
List<T> result = new ArrayList<>();
model.getProcesses().forEach(process -> {
process.getFlowElements().forEach(flowElement -> {
if (flowElement.getClass().isAssignableFrom(clazz)) {
result.add((T) flowElement);
}
});
});
return result;
}
public static StartEvent getStartEvent(BpmnModel model) {
Process process = model.getMainProcess();
// 从 initialFlowElement 找
FlowElement startElement = process.getInitialFlowElement();
if (startElement instanceof StartEvent) {
return (StartEvent) startElement;
}
// 从 flowElementList 找
return (StartEvent) CollUtil.findOne(process.getFlowElements(), flowElement -> flowElement instanceof StartEvent);
}
public static BpmnModel getBpmnModel(byte[] bpmnBytes) {
if (ArrayUtil.isEmpty(bpmnBytes)) {
return null;
}
BpmnXMLConverter converter = new BpmnXMLConverter();
// 补充说明:由于在 Flowable 中自定义了属性,所以 validateSchema 传递 false
return converter.convertToBpmnModel(new BytesStreamSource(bpmnBytes), false, false);
}
public static String getBpmnXml(BpmnModel model) {
if (model == null) {
return null;
}
BpmnXMLConverter converter = new BpmnXMLConverter();
return new String(converter.convertToXML(model));
}
// ========== 遍历相关的方法 ==========
/**
* 找到 source 节点之前的所有用户任务节点
*
* @param source 起始节点
* @param hasSequenceFlow 已经经过的连线的 ID用于判断线路是否重复
* @param userTaskList 已找到的用户任务节点
* @return 用户任务节点 数组
*/
public static List<UserTask> getPreviousUserTaskList(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
// 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
if (source instanceof StartEvent && source.getSubProcess() != null) {
userTaskList = getPreviousUserTaskList(source.getSubProcess(), hasSequenceFlow, userTaskList);
}
// 根据类型,获取入口连线
List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
if (sequenceFlows == null) {
return userTaskList;
}
// 循环找到目标元素
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复,说明循环了,跳过这个循环
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
}
// 添加已经走过的连线
hasSequenceFlow.add(sequenceFlow.getId());
// 类型为用户节点,则新增父级节点
if (sequenceFlow.getSourceFlowElement() instanceof UserTask) {
userTaskList.add((UserTask) sequenceFlow.getSourceFlowElement());
}
// 类型为子流程,则添加子流程开始节点出口处相连的节点
if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) {
// 获取子流程用户任务节点
List<UserTask> childUserTaskList = findChildProcessUserTaskList((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, null);
// 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续
if (CollUtil.isNotEmpty(childUserTaskList)) {
userTaskList.addAll(childUserTaskList);
}
}
// 继续迭代
userTaskList = getPreviousUserTaskList(sequenceFlow.getSourceFlowElement(), hasSequenceFlow, userTaskList);
}
return userTaskList;
}
/**
* 迭代获取子流程用户任务节点
*
* @param source 起始节点
* @param hasSequenceFlow 已经经过的连线的 ID用于判断线路是否重复
* @param userTaskList 需要撤回的用户任务列表
* @return 用户任务节点
*/
public static List<UserTask> findChildProcessUserTaskList(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
// 根据类型,获取出口连线
List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
if (sequenceFlows == null) {
return userTaskList;
}
// 循环找到目标元素
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复,说明循环了,跳过这个循环
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
}
// 添加已经走过的连线
hasSequenceFlow.add(sequenceFlow.getId());
// 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加
if (sequenceFlow.getTargetFlowElement() instanceof UserTask) {
userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement());
continue;
}
// 如果节点为子流程节点情况,则从节点中的第一个节点开始获取
if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
List<UserTask> childUserTaskList = findChildProcessUserTaskList((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, null);
// 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续
if (CollUtil.isNotEmpty(childUserTaskList)) {
userTaskList.addAll(childUserTaskList);
continue;
}
}
// 继续迭代
userTaskList = findChildProcessUserTaskList(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, userTaskList);
}
return userTaskList;
}
/**
* 迭代从后向前扫描,判断目标节点相对于当前节点是否是串行
* 不存在直接回退到子流程中的情况,但存在从子流程出去到父流程情况
*
* @param source 起始节点
* @param target 目标节点
* @param visitedElements 已经经过的连线的 ID用于判断线路是否重复
* @return 结果
*/
public static boolean isSequentialReachable(FlowElement source, FlowElement target, Set<String> visitedElements) {
visitedElements = visitedElements == null ? new HashSet<>() : visitedElements;
// 不能是开始事件和子流程
if (source instanceof StartEvent && isInEventSubprocess(source)) {
return false;
}
// 根据类型,获取入口连线
List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
if (CollUtil.isEmpty(sequenceFlows)) {
return true;
}
// 循环找到目标元素
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复,说明循环了,跳过这个循环
if (visitedElements.contains(sequenceFlow.getId())) {
continue;
}
// 添加已经走过的连线
visitedElements.add(sequenceFlow.getId());
// 这条线路存在目标节点,这条线路完成,进入下个线路
FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement();
if (target.getId().equals(sourceFlowElement.getId())) {
continue;
}
// 如果目标节点为并行网关,则不继续
if (sourceFlowElement instanceof ParallelGateway) {
return false;
}
// 否则就继续迭代
if (!isSequentialReachable(sourceFlowElement, target, visitedElements)) {
return false;
}
}
return true;
}
/**
* 判断当前节点是否属于不同的子流程
*
* @param flowElement 被判断的节点
* @return true 表示属于子流程
*/
private static boolean isInEventSubprocess(FlowElement flowElement) {
FlowElementsContainer flowElementsContainer = flowElement.getParentContainer();
while (flowElementsContainer != null) {
if (flowElementsContainer instanceof EventSubProcess) {
return true;
}
if (flowElementsContainer instanceof FlowElement) {
flowElementsContainer = ((FlowElement) flowElementsContainer).getParentContainer();
} else {
flowElementsContainer = null;
}
}
return false;
}
/**
* 根据正在运行的任务节点,迭代获取子级任务节点列表,向后找
*
* @param source 起始节点
* @param runTaskKeyList 正在运行的任务 Key用于校验任务节点是否是正在运行的节点
* @param hasSequenceFlow 已经经过的连线的 ID用于判断线路是否重复
* @param userTaskList 需要撤回的用户任务列表
* @return 子级任务节点列表
*/
public static List<UserTask> iteratorFindChildUserTasks(FlowElement source, List<String> runTaskKeyList,
Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
// 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
if (source instanceof StartEvent && source.getSubProcess() != null) {
userTaskList = iteratorFindChildUserTasks(source.getSubProcess(), runTaskKeyList, hasSequenceFlow, userTaskList);
}
// 根据类型,获取出口连线
List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
if (sequenceFlows == null) {
return userTaskList;
}
// 循环找到目标元素
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复,说明循环了,跳过这个循环
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
}
// 添加已经走过的连线
hasSequenceFlow.add(sequenceFlow.getId());
// 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加
if (sequenceFlow.getTargetFlowElement() instanceof UserTask && runTaskKeyList.contains((sequenceFlow.getTargetFlowElement()).getId())) {
userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement());
continue;
}
// 如果节点为子流程节点情况,则从节点中的第一个节点开始获取
if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
List<UserTask> childUserTaskList = iteratorFindChildUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), runTaskKeyList, hasSequenceFlow, null);
// 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续
if (CollUtil.isNotEmpty(childUserTaskList)) {
userTaskList.addAll(childUserTaskList);
continue;
}
}
// 继续迭代
userTaskList = iteratorFindChildUserTasks(sequenceFlow.getTargetFlowElement(), runTaskKeyList, hasSequenceFlow, userTaskList);
}
return userTaskList;
}
}

View File

@@ -0,0 +1,178 @@
package com.njcn.bpm.utils;
import com.njcn.bpm.enums.BpmConstants;
import org.flowable.common.engine.api.delegate.Expression;
import org.flowable.common.engine.api.variable.VariableContainer;
import org.flowable.common.engine.impl.el.ExpressionManager;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.engine.ProcessEngineConfiguration;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.TaskInfo;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Flowable 相关的工具方法
*
* @author 芋道源码
*/
public class FlowableUtils {
// ========== User 相关的工具方法 ==========
public static void setAuthenticatedUserId(Long userId) {
Authentication.setAuthenticatedUserId(String.valueOf(userId));
}
public static void clearAuthenticatedUserId() {
Authentication.setAuthenticatedUserId(null);
}
public static String getTenantId() {
return ProcessEngineConfiguration.NO_TENANT_ID;
}
// ========== Execution 相关的工具方法 ==========
/**
* 格式化多实例(并签、或签)的 collectionVariable 变量(多实例对应的多审批人列表)
*
* @param activityId 活动编号
* @return collectionVariable 变量
*/
public static String formatExecutionCollectionVariable(String activityId) {
return activityId + "_assignees";
}
/**
* 格式化多实例(并签、或签)的 collectionElementVariable 变量(当前实例对应的一个审批人)
*
* @param activityId 活动编号
* @return collectionElementVariable 变量
*/
public static String formatExecutionCollectionElementVariable(String activityId) {
return activityId + "_assignee";
}
// ========== ProcessInstance 相关的工具方法 ==========
public static Integer getProcessInstanceStatus(ProcessInstance processInstance) {
return getProcessInstanceStatus(processInstance.getProcessVariables());
}
public static Integer getProcessInstanceStatus(HistoricProcessInstance processInstance) {
return getProcessInstanceStatus(processInstance.getProcessVariables());
}
/**
* 获得流程实例的状态
*
* @param processVariables 流程实例的 variables
* @return 状态
*/
private static Integer getProcessInstanceStatus(Map<String, Object> processVariables) {
return (Integer) processVariables.get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS);
}
/**
* 获得流程实例的表单
*
* @param processInstance 流程实例
* @return 表单
*/
public static Map<String, Object> getProcessInstanceFormVariable(HistoricProcessInstance processInstance) {
Map<String, Object> formVariables = new HashMap<>(processInstance.getProcessVariables());
filterProcessInstanceFormVariable(formVariables);
return formVariables;
}
/**
* 过滤流程实例的表单
*
* 为什么要过滤?目前使用 processVariables 存储所有流程实例的拓展字段,需要过滤掉一部分的系统字段,从而实现表单的展示
*
* @param processVariables 流程实例的 variables
* @return 过滤后的表单
*/
public static Map<String, Object> filterProcessInstanceFormVariable(Map<String, Object> processVariables) {
processVariables.remove(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS);
return processVariables;
}
/**
* 获得流程实例的发起用户选择的审批人 Map
*
* @param processInstance 流程实例
* @return 发起用户选择的审批人 Map
*/
@SuppressWarnings("unchecked")
public static Map<String, List<String>> getStartUserSelectAssignees(ProcessInstance processInstance) {
return (Map<String, List<String>>) processInstance.getProcessVariables().get(
BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES);
}
// ========== Task 相关的工具方法 ==========
/**
* 获得任务的状态
*
* @param task 任务
* @return 状态
*/
public static Integer getTaskStatus(TaskInfo task) {
return (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS);
}
/**
* 获得任务的审批原因
*
* @param task 任务
* @return 审批原因
*/
public static String getTaskReason(TaskInfo task) {
return (String) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_REASON);
}
/**
* 获得任务的表单
*
* @param task 任务
* @return 表单
*/
public static Map<String, Object> getTaskFormVariable(TaskInfo task) {
Map<String, Object> formVariables = new HashMap<>(task.getTaskLocalVariables());
filterTaskFormVariable(formVariables);
return formVariables;
}
/**
* 过滤任务的表单
*
* 为什么要过滤?目前使用 taskLocalVariables 存储所有任务的拓展字段,需要过滤掉一部分的系统字段,从而实现表单的展示
*
* @param taskLocalVariables 任务的 taskLocalVariables
* @return 过滤后的表单
*/
public static Map<String, Object> filterTaskFormVariable(Map<String, Object> taskLocalVariables) {
taskLocalVariables.remove(BpmConstants.TASK_VARIABLE_STATUS);
taskLocalVariables.remove(BpmConstants.TASK_VARIABLE_REASON);
return taskLocalVariables;
}
// ========== Expression 相关的工具方法 ==========
public static Object getExpressionValue(VariableContainer variableContainer, String expressionString) {
ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration();
assert processEngineConfiguration != null;
ExpressionManager expressionManager = processEngineConfiguration.getExpressionManager();
assert expressionManager != null;
Expression expression = expressionManager.createExpression(expressionString);
return expression.getValue(variableContainer);
}
}

View File

@@ -0,0 +1,202 @@
package com.njcn.bpm.utils;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
/**
* JSON 工具类
*
* @author 芋道源码
*/
@Slf4j
public class JsonUtils {
private static ObjectMapper objectMapper = new ObjectMapper();
static {
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 忽略 null 值
objectMapper.registerModules(new JavaTimeModule()); // 解决 LocalDateTime 的序列化
}
/**
* 初始化 objectMapper 属性
* <p>
* 通过这样的方式,使用 Spring 创建的 ObjectMapper Bean
*
* @param objectMapper ObjectMapper 对象
*/
public static void init(ObjectMapper objectMapper) {
JsonUtils.objectMapper = objectMapper;
}
@SneakyThrows
public static String toJsonString(Object object) {
return objectMapper.writeValueAsString(object);
}
@SneakyThrows
public static byte[] toJsonByte(Object object) {
return objectMapper.writeValueAsBytes(object);
}
@SneakyThrows
public static String toJsonPrettyString(Object object) {
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
}
public static <T> T parseObject(String text, Class<T> clazz) {
if (StrUtil.isEmpty(text)) {
return null;
}
try {
return objectMapper.readValue(text, clazz);
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
public static <T> T parseObject(String text, String path, Class<T> clazz) {
if (StrUtil.isEmpty(text)) {
return null;
}
try {
JsonNode treeNode = objectMapper.readTree(text);
JsonNode pathNode = treeNode.path(path);
return objectMapper.readValue(pathNode.toString(), clazz);
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
public static <T> T parseObject(String text, Type type) {
if (StrUtil.isEmpty(text)) {
return null;
}
try {
return objectMapper.readValue(text, objectMapper.getTypeFactory().constructType(type));
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
/**
* 将字符串解析成指定类型的对象
* 使用 {@link #parseObject(String, Class)} 时,在@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) 的场景下,
* 如果 text 没有 class 属性,则会报错。此时,使用这个方法,可以解决。
*
* @param text 字符串
* @param clazz 类型
* @return 对象
*/
public static <T> T parseObject2(String text, Class<T> clazz) {
if (StrUtil.isEmpty(text)) {
return null;
}
return JSONUtil.toBean(text, clazz);
}
public static <T> T parseObject(byte[] bytes, Class<T> clazz) {
if (ArrayUtil.isEmpty(bytes)) {
return null;
}
try {
return objectMapper.readValue(bytes, clazz);
} catch (IOException e) {
log.error("json parse err,json:{}", bytes, e);
throw new RuntimeException(e);
}
}
public static <T> T parseObject(String text, TypeReference<T> typeReference) {
try {
return objectMapper.readValue(text, typeReference);
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
/**
* 解析 JSON 字符串成指定类型的对象,如果解析失败,则返回 null
*
* @param text 字符串
* @param typeReference 类型引用
* @return 指定类型的对象
*/
public static <T> T parseObjectQuietly(String text, TypeReference<T> typeReference) {
try {
return objectMapper.readValue(text, typeReference);
} catch (IOException e) {
return null;
}
}
public static <T> List<T> parseArray(String text, Class<T> clazz) {
if (StrUtil.isEmpty(text)) {
return new ArrayList<>();
}
try {
return objectMapper.readValue(text, objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
public static <T> List<T> parseArray(String text, String path, Class<T> clazz) {
if (StrUtil.isEmpty(text)) {
return null;
}
try {
JsonNode treeNode = objectMapper.readTree(text);
JsonNode pathNode = treeNode.path(path);
return objectMapper.readValue(pathNode.toString(), objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
public static JsonNode parseTree(String text) {
try {
return objectMapper.readTree(text);
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
public static JsonNode parseTree(byte[] text) {
try {
return objectMapper.readTree(text);
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
public static boolean isJson(String text) {
return JSONUtil.isTypeJSON(text);
}
}

View File

@@ -0,0 +1,83 @@
package com.njcn.bpm.utils;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 字符串工具类
*
* @author 芋道源码
*/
public class StrUtils {
public static String maxLength(CharSequence str, int maxLength) {
return StrUtil.maxLength(str, maxLength - 3); // -3 的原因,是该方法会补充 ... 恰好
}
/**
* 给定字符串是否以任何一个字符串开始
* 给定字符串和数组为空都返回 false
*
* @param str 给定字符串
* @param prefixes 需要检测的开始字符串
* @since 3.0.6
*/
public static boolean startWithAny(String str, Collection<String> prefixes) {
if (StrUtil.isEmpty(str) || ArrayUtil.isEmpty(prefixes)) {
return false;
}
for (CharSequence suffix : prefixes) {
if (StrUtil.startWith(str, suffix, false)) {
return true;
}
}
return false;
}
public static List<Long> splitToLong(String value, CharSequence separator) {
long[] longs = StrUtil.splitToLong(value, separator);
return Arrays.stream(longs).boxed().collect(Collectors.toList());
}
public static Set<Long> splitToLongSet(String value) {
return splitToLongSet(value, StrPool.COMMA);
}
public static Set<Long> splitToLongSet(String value, CharSequence separator) {
long[] longs = StrUtil.splitToLong(value, separator);
return Arrays.stream(longs).boxed().collect(Collectors.toSet());
}
public static List<Integer> splitToInteger(String value, CharSequence separator) {
int[] integers = StrUtil.splitToInt(value, separator);
return Arrays.stream(integers).boxed().collect(Collectors.toList());
}
/**
* 移除字符串中,包含指定字符串的行
*
* @param content 字符串
* @param sequence 包含的字符串
* @return 移除后的字符串
*/
public static String removeLineContains(String content, String sequence) {
if (StrUtil.isEmpty(content) || StrUtil.isEmpty(sequence)) {
return content;
}
return Arrays.stream(content.split("\n"))
.filter(line -> !line.contains(sequence))
.collect(Collectors.joining("\n"));
}
public static List<String> splitToStringList(String value) {
return StrUtil.split(value, StrPool.COMMA);
}
}