工作流模块提交

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,27 @@
package com.njcn.bpm;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.DependsOn;
/**
* pqs
*
* @author cdf
* @date 2022/11/10
*/
@Slf4j
@DependsOn("proxyMapperRegister")
@MapperScan("com.njcn.**.mapper")
@EnableFeignClients(basePackages = "com.njcn")
@SpringBootApplication(scanBasePackages = "com.njcn")
public class BpmApplication {
public static void main(String[] args) {
SpringApplication.run(BpmApplication.class, args);
}
}

View File

@@ -0,0 +1,47 @@
package com.njcn.bpm.behavior;
import com.njcn.bpm.strategy.BpmTaskCandidateInvoker;
import lombok.Setter;
import org.flowable.bpmn.model.Activity;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
import org.flowable.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
import org.flowable.engine.impl.bpmn.parser.factory.DefaultActivityBehaviorFactory;
/**
* 自定义的 ActivityBehaviorFactory 实现类,目的如下:
* 1. 自定义 {@link #createUserTaskActivityBehavior(UserTask)}:实现自定义的流程任务的 assignee 负责人的分配
*
* @author 芋道源码
*/
@Setter
public class BpmActivityBehaviorFactory extends DefaultActivityBehaviorFactory {
private BpmTaskCandidateInvoker taskCandidateInvoker;
@Override
public UserTaskActivityBehavior createUserTaskActivityBehavior(UserTask userTask) {
BpmUserTaskActivityBehavior bpmUserTaskActivityBehavior = new BpmUserTaskActivityBehavior(userTask);
bpmUserTaskActivityBehavior.setTaskCandidateInvoker(taskCandidateInvoker);
return bpmUserTaskActivityBehavior;
}
@Override
public ParallelMultiInstanceBehavior createParallelMultiInstanceBehavior(Activity activity,
AbstractBpmnActivityBehavior behavior) {
BpmParallelMultiInstanceBehavior bpmParallelMultiInstanceBehavior = new BpmParallelMultiInstanceBehavior(activity, behavior);
bpmParallelMultiInstanceBehavior.setTaskCandidateInvoker(taskCandidateInvoker);
return bpmParallelMultiInstanceBehavior;
}
@Override
public SequentialMultiInstanceBehavior createSequentialMultiInstanceBehavior(Activity activity,
AbstractBpmnActivityBehavior behavior) {
BpmSequentialMultiInstanceBehavior bpmSequentialMultiInstanceBehavior = new BpmSequentialMultiInstanceBehavior(activity, behavior);
bpmSequentialMultiInstanceBehavior.setTaskCandidateInvoker(taskCandidateInvoker);
return bpmSequentialMultiInstanceBehavior;
}
}

View File

@@ -0,0 +1,57 @@
package com.njcn.bpm.behavior;
import com.njcn.bpm.strategy.BpmTaskCandidateInvoker;
import com.njcn.bpm.utils.FlowableUtils;
import lombok.Setter;
import org.flowable.bpmn.model.Activity;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
import java.util.List;
import java.util.Set;
/**
* 自定义的【并行】的【多个】流程任务的 assignee 负责人的分配
* 第一步,基于分配规则,计算出分配任务的【多个】候选人们。
* 第二步,将【多个】任务候选人们,设置到 DelegateExecution 的 collectionVariable 变量中,以便 BpmUserTaskActivityBehavior 使用它
*
* @author kemengkai
* @since 2022-04-21 16:57
*/
@Setter
public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehavior {
private BpmTaskCandidateInvoker taskCandidateInvoker;
public BpmParallelMultiInstanceBehavior(Activity activity,
AbstractBpmnActivityBehavior innerActivityBehavior) {
super(activity, innerActivityBehavior);
}
/**
* 重写该方法,主要实现两个功能:
* 1. 忽略原有的 collectionVariable、collectionElementVariable 表达式,而是采用自己定义的
* 2. 获得任务的处理人,并设置到 collectionVariable 中,用于 BpmUserTaskActivityBehavior 从中可以获取任务的处理人
*
* 注意,多个任务实例,每个任务实例对应一个处理人,所以返回的数量就是任务处理人的数量
*
* @param execution 执行任务
* @return 数量
*/
@Override
protected int resolveNrOfInstances(DelegateExecution execution) {
// 第一步,设置 collectionVariable 和 CollectionVariable
// 从 execution.getVariable() 读取所有任务处理人的 key
super.collectionExpression = null; // collectionExpression 和 collectionVariable 是互斥的
super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId());
// 从 execution.getVariable() 读取当前所有任务处理的人的 key
super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
// 第二步,获取任务的所有处理人
List<String> assigneeUserIds = taskCandidateInvoker.calculateUsers(execution);
execution.setVariable(super.collectionVariable, assigneeUserIds);
return assigneeUserIds.size();
}
}

View File

@@ -0,0 +1,52 @@
package com.njcn.bpm.behavior;
import com.njcn.bpm.strategy.BpmTaskCandidateInvoker;
import com.njcn.bpm.utils.FlowableUtils;
import lombok.Setter;
import org.flowable.bpmn.model.Activity;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* 自定义的【串行】的【多个】流程任务的 assignee 负责人的分配
*
* 本质上,实现和 {@link BpmParallelMultiInstanceBehavior} 一样,只是继承的类不一样
*
* @author 芋道源码
*/
@Setter
public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceBehavior {
private BpmTaskCandidateInvoker taskCandidateInvoker;
public BpmSequentialMultiInstanceBehavior(Activity activity, AbstractBpmnActivityBehavior innerActivityBehavior) {
super(activity, innerActivityBehavior);
}
/**
* 逻辑和 {@link BpmParallelMultiInstanceBehavior#resolveNrOfInstances(DelegateExecution)} 类似
*
* 差异的点:是在【第二步】的时候,需要返回 LinkedHashSet 集合!因为它需要有序!
*/
@Override
protected int resolveNrOfInstances(DelegateExecution execution) {
// 第一步,设置 collectionVariable 和 CollectionVariable
// 从 execution.getVariable() 读取所有任务处理人的 key
super.collectionExpression = null; // collectionExpression 和 collectionVariable 是互斥的
super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId());
// 从 execution.getVariable() 读取当前所有任务处理的人的 key
super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
// 第二步,获取任务的所有处理人
List<String> assigneeUserIds = new ArrayList<>(taskCandidateInvoker.calculateUsers(execution)); // 保证有序!!!
execution.setVariable(super.collectionVariable, assigneeUserIds);
return assigneeUserIds.size();
}
}

View File

@@ -0,0 +1,67 @@
package com.njcn.bpm.behavior;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.RandomUtil;
import com.njcn.bpm.strategy.BpmTaskCandidateInvoker;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.UserTask;
import org.flowable.common.engine.impl.el.ExpressionManager;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.util.TaskHelper;
import org.flowable.task.service.TaskService;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import java.util.List;
import java.util.Set;
/**
* 自定义的【单个】流程任务的 assignee 负责人的分配
* 第一步,基于分配规则,计算出分配任务的【单个】候选人。如果找不到,则直接报业务异常,不继续执行后续的流程;
* 第二步,随机选择一个候选人,则选择作为 assignee 负责人。
*
* @author 芋道源码
*/
@Slf4j
public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior {
@Setter
private BpmTaskCandidateInvoker taskCandidateInvoker;
public BpmUserTaskActivityBehavior(UserTask userTask) {
super(userTask);
}
@Override
protected void handleAssignments(TaskService taskService, String assignee, String owner,
List<String> candidateUsers, List<String> candidateGroups, TaskEntity task, ExpressionManager expressionManager,
DelegateExecution execution, ProcessEngineConfigurationImpl processEngineConfiguration) {
// 第一步,获得任务的候选用户
String assigneeUserId = calculateTaskCandidateUsers(execution);
Assert.notNull(assigneeUserId, "任务处理人不能为空");
// 第二步,设置作为负责人
TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserId));
}
private String calculateTaskCandidateUsers(DelegateExecution execution) {
// 情况一,如果是多实例的任务,例如说会签、或签等情况,则从 Variable 中获取。
// 顺序审批可见 BpmSequentialMultiInstanceBehavior并发审批可见 BpmSequentialMultiInstanceBehavior
if (super.multiInstanceActivityBehavior != null) {
return execution.getVariable(super.multiInstanceActivityBehavior.getCollectionElementVariable(), String.class);
}
// 情况二,如果非多实例的任务,则计算任务处理人
// 第一步,先计算可处理该任务的处理人们
List<String> candidateUserIds = taskCandidateInvoker.calculateUsers(execution);
// 第二步,后随机选择一个任务的处理人
// 疑问:为什么一定要选择一个任务处理人?
// 解答:项目对 bpm 的任务是责任到人,所以每个任务有且仅有一个处理人。
// 如果希望一个任务可以同时被多个人处理,可以考虑使用 BpmParallelMultiInstanceBehavior 实现的会签 or 或签。
int index = RandomUtil.randomInt(candidateUserIds.size());
return CollUtil.get(candidateUserIds, index);
}
}

View File

@@ -0,0 +1,91 @@
package com.njcn.bpm.config;
import cn.hutool.core.collection.ListUtil;
import com.njcn.bpm.behavior.BpmActivityBehaviorFactory;
import com.njcn.bpm.event.BpmProcessInstanceEventPublisher;
import com.njcn.bpm.strategy.BpmTaskCandidateInvoker;
import com.njcn.bpm.strategy.IBpmTaskCandidateStrategy;
import com.njcn.user.api.UserFeignClient;
import org.flowable.common.engine.api.delegate.event.FlowableEventListener;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.AsyncListenableTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.List;
/**
* BPM 模块的 Flowable 配置类
*
* @author jason
*/
@Configuration(proxyBeanMethods = false)
public class BpmFlowableConfiguration {
/**
* 参考 {@link org.flowable.spring.boot.FlowableJobConfiguration} 类,创建对应的 AsyncListenableTaskExecutor Bean
*
* 如果不创建会导致项目启动时Flowable 报错的问题
*/
@Bean(name = "applicationTaskExecutor")
@ConditionalOnMissingBean(name = "applicationTaskExecutor")
public AsyncListenableTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(8);
executor.setMaxPoolSize(8);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("flowable-task-Executor-");
executor.setAwaitTerminationSeconds(30);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAllowCoreThreadTimeOut(true);
executor.initialize();
return executor;
}
/**
* BPM 模块的 ProcessEngineConfigurationConfigurer 实现类:
*
* 1. 设置各种监听器
* 2. 设置自定义的 ActivityBehaviorFactory 实现
*/
@Bean
public EngineConfigurationConfigurer<SpringProcessEngineConfiguration> bpmProcessEngineConfigurationConfigurer(
ObjectProvider<FlowableEventListener> listeners,
BpmActivityBehaviorFactory bpmActivityBehaviorFactory) {
return configuration -> {
// 注册监听器,例如说 BpmActivityEventListener
configuration.setEventListeners(ListUtil.toList(listeners.iterator()));
// 设置 ActivityBehaviorFactory 实现类,用于流程任务的审核人的自定义
configuration.setActivityBehaviorFactory(bpmActivityBehaviorFactory);
};
}
// =========== 审批人相关的 Bean ==========
@Bean
public BpmActivityBehaviorFactory bpmActivityBehaviorFactory(BpmTaskCandidateInvoker bpmTaskCandidateInvoker) {
BpmActivityBehaviorFactory bpmActivityBehaviorFactory = new BpmActivityBehaviorFactory();
bpmActivityBehaviorFactory.setTaskCandidateInvoker(bpmTaskCandidateInvoker);
return bpmActivityBehaviorFactory;
}
@Bean
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") // adminUserApi 可以注入成功
public BpmTaskCandidateInvoker bpmTaskCandidateInvoker(List<IBpmTaskCandidateStrategy> strategyList,
UserFeignClient adminUserApi) {
return new BpmTaskCandidateInvoker(strategyList, adminUserApi);
}
// =========== 自己拓展的 Bean ==========
@Bean
public BpmProcessInstanceEventPublisher processInstanceEventPublisher(ApplicationEventPublisher publisher) {
return new BpmProcessInstanceEventPublisher(publisher);
}
}

View File

@@ -0,0 +1,111 @@
package com.njcn.bpm.controller;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.bpm.pojo.param.BpmCategoryParam;
import com.njcn.bpm.pojo.po.BpmCategory;
import com.njcn.bpm.pojo.vo.BpmCategoryVO;
import com.njcn.bpm.service.IBpmCategoryService;
import com.njcn.common.pojo.annotation.OperateInfo;
import com.njcn.common.pojo.constant.OperateType;
import com.njcn.common.pojo.enums.common.LogEnum;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.response.HttpResult;
import com.njcn.common.utils.HttpResultUtil;
import com.njcn.common.utils.LogUtil;
import com.njcn.web.controller.BaseController;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
@RestController
@RequestMapping("/bpm/category")
@Validated
@Slf4j
@Api(tags = "流程分类控制器")
@RequiredArgsConstructor
public class BpmCategoryController extends BaseController {
private final IBpmCategoryService categoryService;
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.ADD)
@PostMapping("/add")
@ApiOperation("创建流程分类")
@ApiImplicitParam(name = "bpmCategoryParam", value = "流程分类数据", required = true)
public HttpResult<String> add(@Valid @RequestBody BpmCategoryParam bpmCategoryParam) {
String methodDescribe = getMethodDescribe("add");
String categoryId = categoryService.createCategory(bpmCategoryParam);
if (StrUtil.isNotBlank(categoryId)) {
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, categoryId, methodDescribe);
} else {
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, null, methodDescribe);
}
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPDATE)
@PostMapping("/update")
@ApiOperation("更新流程分类")
@ApiImplicitParam(name = "updateParam", value = "流程分类数据", required = true)
public HttpResult<Object> update(@RequestBody @Validated BpmCategoryParam.BpmCategoryUpdateParam updateParam) {
String methodDescribe = getMethodDescribe("update");
categoryService.updateCategory(updateParam);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPDATE)
@PostMapping("/delete")
@ApiOperation("删除流程分类")
@ApiImplicitParam(name = "ids", value = "流程分类索引", required = true, dataTypeClass = List.class)
public HttpResult<Object> delete(@RequestBody List<String> ids) {
String methodDescribe = getMethodDescribe("delete");
LogUtil.njcnDebug(log, "{}流程分类ID数据为{}", methodDescribe, String.join(StrUtil.COMMA, ids));
categoryService.deleteCategory(ids);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe);
}
@GetMapping("/getById")
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@Operation(summary = "获得流程分类")
@Parameter(name = "id", description = "编号", required = true)
public HttpResult<BpmCategory> getById(String id) {
String methodDescribe = getMethodDescribe("getById");
BpmCategory bpmCategory = categoryService.getById(id);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, bpmCategory, methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@PostMapping("/list")
@ApiOperation("查询流程分类数据")
@ApiImplicitParam(name = "bpmCategoryQueryParam", value = "查询参数", required = true)
public HttpResult<Page<BpmCategoryVO>> list(@RequestBody BpmCategoryParam.BpmCategoryQueryParam bpmCategoryQueryParam) {
String methodDescribe = getMethodDescribe("list");
LogUtil.njcnDebug(log, "{},查询流程分类数据为:{}", methodDescribe, bpmCategoryQueryParam);
Page<BpmCategoryVO> result = categoryService.getCategoryPage(bpmCategoryQueryParam);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
@GetMapping("/simpleList")
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@Operation(summary = "获得动态表单的精简列表", description = "用于表单下拉框")
public HttpResult<List<BpmCategory>> getCategorySimpleList() {
String methodDescribe = getMethodDescribe("getCategorySimpleList");
List<BpmCategory> list = categoryService.getCategoryList();
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, list, methodDescribe);
}
}

View File

@@ -0,0 +1,112 @@
package com.njcn.bpm.controller;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.bpm.pojo.param.BpmFormParam;
import com.njcn.bpm.pojo.po.BpmForm;
import com.njcn.bpm.pojo.vo.BpmFormVO;
import com.njcn.bpm.service.IBpmFormService;
import com.njcn.common.pojo.annotation.OperateInfo;
import com.njcn.common.pojo.constant.OperateType;
import com.njcn.common.pojo.enums.common.LogEnum;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.response.HttpResult;
import com.njcn.common.utils.HttpResultUtil;
import com.njcn.common.utils.LogUtil;
import com.njcn.web.controller.BaseController;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
@RestController
@RequestMapping("/bpm/form")
@Validated
@Slf4j
@Api(tags = "流程表单控制器")
@RequiredArgsConstructor
public class BpmFormController extends BaseController {
private final IBpmFormService formService;
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@PostMapping("/list")
@ApiOperation("查询流程表单数据")
@ApiImplicitParam(name = "bpmFormQueryParam", value = "查询参数", required = true)
public HttpResult<Page<BpmFormVO>> list(@RequestBody BpmFormParam.BpmFormQueryParam bpmFormQueryParam) {
String methodDescribe = getMethodDescribe("list");
LogUtil.njcnDebug(log, "{},查询流程表单数据为:{}", methodDescribe, bpmFormQueryParam);
Page<BpmFormVO> result = formService.getFormPage(bpmFormQueryParam);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.ADD)
@PostMapping("/add")
@ApiOperation("新增流程表单")
@ApiImplicitParam(name = "bpmFormParam", value = "流程表单数据", required = true)
public HttpResult<String> add(@Valid @RequestBody BpmFormParam bpmFormParam) {
String methodDescribe = getMethodDescribe("add");
String wfFormId = formService.createForm(bpmFormParam);
if (StrUtil.isNotBlank(wfFormId)) {
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, wfFormId, methodDescribe);
} else {
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, null, methodDescribe);
}
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPDATE)
@PostMapping("/update")
@ApiOperation("更新流程表单")
@ApiImplicitParam(name = "updateParam", value = "流程表单数据", required = true)
public HttpResult<Object> update(@RequestBody @Validated BpmFormParam.BpmFormUpdateParam updateParam) {
String methodDescribe = getMethodDescribe("update");
formService.updateForm(updateParam);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPDATE)
@PostMapping("/delete")
@ApiOperation("删除流程表单")
@ApiImplicitParam(name = "ids", value = "流程表单索引", required = true, dataTypeClass = List.class)
public HttpResult<Object> delete(@RequestBody List<String> ids) {
String methodDescribe = getMethodDescribe("delete");
LogUtil.njcnDebug(log, "{}流程表单ID数据为{}", methodDescribe, String.join(StrUtil.COMMA, ids));
formService.deleteForm(ids);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe);
}
@GetMapping("/getById")
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@Operation(summary = "获得动态表单")
@Parameter(name = "id", description = "编号", required = true)
public HttpResult<BpmForm> getById(String id) {
String methodDescribe = getMethodDescribe("getById");
BpmForm form = formService.getById(id);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, form, methodDescribe);
}
@GetMapping("/simpleList")
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@Operation(summary = "获得动态表单的精简列表", description = "用于表单下拉框")
public HttpResult<List<BpmForm>> getFormSimpleList() {
String methodDescribe = getMethodDescribe("getFormSimpleList");
List<BpmForm> list = formService.getFormList();
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, list, methodDescribe);
}
}

View File

@@ -0,0 +1,174 @@
package com.njcn.bpm.controller;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.bpm.pojo.dto.BpmModelMetaInfoRespDTO;
import com.njcn.bpm.pojo.param.BpmCategoryParam;
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.vo.BpmModelRespVO;
import com.njcn.bpm.service.IBpmCategoryService;
import com.njcn.bpm.service.IBpmModelService;
import com.njcn.bpm.service.IBpmFormService;
import com.njcn.bpm.service.IBpmProcessDefinitionService;
import com.njcn.bpm.utils.BpmModelConvert;
import com.njcn.bpm.utils.CollectionUtils;
import com.njcn.bpm.utils.JsonUtils;
import com.njcn.common.pojo.annotation.OperateInfo;
import com.njcn.common.pojo.constant.OperateType;
import com.njcn.common.pojo.enums.common.LogEnum;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.response.HttpResult;
import com.njcn.common.utils.HttpResultUtil;
import com.njcn.common.utils.LogUtil;
import com.njcn.web.controller.BaseController;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ProcessDefinition;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.njcn.bpm.pojo.dto.CommonResult.success;
import static com.njcn.bpm.utils.CollectionUtils.convertMap;
import static com.njcn.bpm.utils.CollectionUtils.convertSet;
@RestController
@RequestMapping("/bpm/model")
@Validated
@Slf4j
@Api(tags = "流程模型控制器")
@RequiredArgsConstructor
public class BpmModelController extends BaseController {
private final IBpmModelService modelService;
private final IBpmFormService formService;
private final IBpmCategoryService categoryService;
private final IBpmProcessDefinitionService processDefinitionService;
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@PostMapping("/list")
@ApiOperation("获得模型分页")
@ApiImplicitParam(name = "bpmModelQueryParam", value = "查询参数", required = true)
public HttpResult<Page<BpmModelRespVO>> getModelPage(@RequestBody BpmModelParam.BpmModelQueryParam bpmModelQueryParam) {
String methodDescribe = getMethodDescribe("list");
LogUtil.njcnDebug(log, "{},查询流程表单数据为:{}", methodDescribe, bpmModelQueryParam);
Page<Model> pageResult = modelService.getModelPage(bpmModelQueryParam);
if (CollUtil.isEmpty(pageResult.getRecords())) {
Page<BpmModelRespVO> result = new Page<>();
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
// 拼接数据
// 获得 Form 表单
Set<String> formIds = convertSet(pageResult.getRecords(), model -> {
BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class);
return metaInfo != null ? metaInfo.getFormId() : null;
});
Map<String, BpmForm> formMap = formService.getFormMap(formIds);
// 获得 Category Map
Map<String, BpmCategory> categoryMap = categoryService.getCategoryMap(
convertSet(pageResult.getRecords(), Model::getCategory));
// 获得 Deployment Map
Set<String> deploymentIds = new HashSet<>();
pageResult.getRecords().forEach(model -> CollectionUtils.addIfNotNull(deploymentIds, model.getDeploymentId()));
Map<String, Deployment> deploymentMap = processDefinitionService.getDeploymentMap(deploymentIds);
// 获得 ProcessDefinition Map
List<ProcessDefinition> processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds(deploymentIds);
Map<String, ProcessDefinition> processDefinitionMap = convertMap(processDefinitions, ProcessDefinition::getDeploymentId);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, BpmModelConvert.INSTANCE.buildModelPage(pageResult, formMap, categoryMap, deploymentMap, processDefinitionMap), methodDescribe);
}
@GetMapping("/getById")
@Operation(summary = "获得模型")
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@Parameter(name = "id", description = "编号", required = true)
public HttpResult<BpmModelRespVO> getById(String id) {
Model model = modelService.getModel(id);
String methodDescribe = getMethodDescribe("getById");
if (model == null) {
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe);
}
byte[] bpmnBytes = modelService.getModelBpmnXML(id);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, BpmModelConvert.INSTANCE.buildModel(model, bpmnBytes), methodDescribe);
}
@Operation(summary = "新建模型")
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.ADD)
@PostMapping("/add")
@ApiOperation("新建模型")
@ApiImplicitParam(name = "createRetVO", value = "模型数据", required = true)
public HttpResult<String> createModel(@Validated @RequestBody BpmModelParam bpmModelParam) {
String methodDescribe = getMethodDescribe("createModel");
String modelId = modelService.createModel(bpmModelParam, null);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, modelId, methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPDATE)
@PostMapping("/update")
@ApiOperation("修改模型")
@ApiImplicitParam(name = "updateParam", value = "流程分类数据", required = true)
public HttpResult<Object> update( @Validated @RequestBody BpmModelParam.BpmModelUpdateParam updateParam) {
String methodDescribe = getMethodDescribe("update");
modelService.updateModel(updateParam);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe);
}
// @PostMapping("/import")
// @Operation(summary = "导入模型")
// @PreAuthorize("@ss.hasPermission('bpm:model:import')")
// public CommonResult<String> importModel(@Valid BpmModeImportReqVO importReqVO) throws IOException {
// BpmModelCreateReqVO createReqVO = BeanUtils.toBean(importReqVO, BpmModelCreateReqVO.class);
// // 读取文件
// String bpmnXml = IoUtils.readUtf8(importReqVO.getBpmnFile().getInputStream(), false);
// return success(modelService.createModel(createReqVO, bpmnXml));
// }
@PostMapping("/deploy")
@Operation(summary = "部署模型")
@Parameter(name = "id", description = "编号", required = true)
public HttpResult<Boolean> deployModel(String id) {
String methodDescribe = getMethodDescribe("deployModel");
modelService.deployModel(id);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe);
}
//
// @PutMapping("/update-state")
// @Operation(summary = "修改模型的状态", description = "实际更新的部署的流程定义的状态")
// @PreAuthorize("@ss.hasPermission('bpm:model:update')")
// public CommonResult<Boolean> updateModelState(@Valid @RequestBody BpmModelUpdateStateReqVO reqVO) {
// modelService.updateModelState(reqVO.getId(), reqVO.getState());
// return success(true);
// }
//
// @DeleteMapping("/delete")
// @Operation(summary = "删除模型")
// @Parameter(name = "id", description = "编号", required = true, example = "1024")
// @PreAuthorize("@ss.hasPermission('bpm:model:delete')")
// public CommonResult<Boolean> deleteModel(@RequestParam("id") String id) {
// modelService.deleteModel(id);
// return success(true);
// }
}

View File

@@ -0,0 +1,123 @@
package com.njcn.bpm.controller;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.bpm.pojo.param.BpmProcessDefinitionInfoParam;
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 com.njcn.bpm.service.IBpmCategoryService;
import com.njcn.bpm.service.IBpmFormService;
import com.njcn.bpm.service.IBpmProcessDefinitionService;
import com.njcn.bpm.strategy.BpmTaskCandidateStartUserSelectStrategy;
import com.njcn.bpm.utils.BpmProcessDefinitionConvert;
import com.njcn.common.pojo.annotation.OperateInfo;
import com.njcn.common.pojo.enums.common.LogEnum;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.response.HttpResult;
import com.njcn.common.utils.HttpResultUtil;
import com.njcn.web.controller.BaseController;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static com.njcn.bpm.utils.CollectionUtils.convertSet;
@RestController
@RequestMapping("/bpm/processDefinition")
@Validated
@Slf4j
@Api(tags = "管理后台 - 流程定义")
@RequiredArgsConstructor
public class BpmProcessDefinitionController extends BaseController {
private final IBpmProcessDefinitionService processDefinitionService;
private final IBpmFormService formService;
private final IBpmCategoryService categoryService;
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@PostMapping("/page")
@ApiOperation("获得流程定义分页")
@ApiImplicitParam(name = "bpmProcessDefinitionInfoQueryParam", value = "查询参数", required = true)
public HttpResult<Page<BpmProcessDefinitionInfoVO>> getProcessDefinitionPage(
BpmProcessDefinitionInfoParam.BpmProcessDefinitionInfoQueryParam bpmProcessDefinitionInfoQueryParam) {
String methodDescribe = getMethodDescribe("getProcessDefinitionPage");
Page<ProcessDefinition> pageResult = processDefinitionService.getProcessDefinitionPage(bpmProcessDefinitionInfoQueryParam);
if (CollUtil.isEmpty(pageResult.getRecords())) {
Page<BpmProcessDefinitionInfoVO> result = new Page<>();
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
// 获得 Category Map
Map<String, BpmCategory> categoryMap = categoryService.getCategoryMap(
convertSet(pageResult.getRecords(), ProcessDefinition::getCategory));
// 获得 Deployment Map
Map<String, Deployment> deploymentMap = processDefinitionService.getDeploymentMap(
convertSet(pageResult.getRecords(), ProcessDefinition::getDeploymentId));
// 获得 BpmProcessDefinitionInfoDO Map
Map<String, BpmProcessDefinitionInfo> processDefinitionMap = processDefinitionService.getProcessDefinitionInfoMap(
convertSet(pageResult.getRecords(), ProcessDefinition::getId));
// 获得 Form Map
Map<String, BpmForm> formMap = formService.getFormMap(
convertSet(processDefinitionMap.values(), BpmProcessDefinitionInfo::getFormId));
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinitionPage(
pageResult, deploymentMap, processDefinitionMap, formMap, categoryMap), methodDescribe);
}
@GetMapping("/list")
@Operation(summary = "获得流程定义列表")
@Parameter(name = "suspensionState", description = "挂起状态", required = true) // 参见 Flowable SuspensionState 枚举
public HttpResult<List<BpmProcessDefinitionInfoVO>> getProcessDefinitionList(Integer suspensionState) {
String methodDescribe = getMethodDescribe("getProcessDefinitionList");
List<ProcessDefinition> list = processDefinitionService.getProcessDefinitionListBySuspensionState(suspensionState);
if (CollUtil.isEmpty(list)) {
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, new ArrayList<>(), methodDescribe);
}
// 获得 BpmProcessDefinitionInfoDO Map
Map<String, BpmProcessDefinitionInfo> processDefinitionMap = processDefinitionService.getProcessDefinitionInfoMap(
convertSet(list, ProcessDefinition::getId));
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinitionList(
list, null, processDefinitionMap, null, null), methodDescribe);
}
@GetMapping("/get")
@Operation(summary = "获得流程定义")
@Parameter(name = "id", description = "流程编号", required = true, example = "1024")
@Parameter(name = "key", description = "流程定义标识", required = true, example = "1024")
public HttpResult<BpmProcessDefinitionInfoVO> getProcessDefinition(
@RequestParam(value = "id", required = false) String id,
@RequestParam(value = "key", required = false) String key) {
String methodDescribe = getMethodDescribe("getProcessDefinition");
ProcessDefinition processDefinition = id != null ? processDefinitionService.getProcessDefinition(id)
: processDefinitionService.getActiveProcessDefinition(key);
if (processDefinition == null) {
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe);
}
BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processDefinition.getId());
List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinition(
processDefinition, null, null, null, null, bpmnModel, userTaskList), methodDescribe);
}
}

View File

@@ -0,0 +1,23 @@
package com.njcn.bpm.event;
import lombok.AllArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.validation.annotation.Validated;
import javax.validation.Valid;
/**
*
* @author 芋道源码
*/
@AllArgsConstructor
@Validated
public class BpmProcessInstanceEventPublisher {
private final ApplicationEventPublisher publisher;
public void sendProcessInstanceResultEvent(@Valid BpmProcessInstanceStatusEvent event) {
publisher.publishEvent(event);
}
}

View File

@@ -0,0 +1,42 @@
package com.njcn.bpm.event;
import lombok.Data;
import org.springframework.context.ApplicationEvent;
import javax.validation.constraints.NotNull;
/**
* 流程实例的状态(结果)发生变化的 Event
*
* @author 芋道源码
*/
@SuppressWarnings("ALL")
@Data
public class BpmProcessInstanceStatusEvent extends ApplicationEvent {
/**
* 流程实例的编号
*/
@NotNull(message = "流程实例的编号不能为空")
private String id;
/**
* 流程实例的 key
*/
@NotNull(message = "流程实例的 key 不能为空")
private String processDefinitionKey;
/**
* 流程实例的结果
*/
@NotNull(message = "流程实例的状态不能为空")
private Integer status;
/**
* 流程实例对应的业务标识
* 例如说,请假
*/
private String businessKey;
public BpmProcessInstanceStatusEvent(Object source) {
super(source);
}
}

View File

@@ -0,0 +1,22 @@
package com.njcn.bpm.mapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.bpm.pojo.po.BpmCategory;
import com.njcn.bpm.pojo.vo.BpmCategoryVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* BPM 流程分类 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface BpmCategoryMapper extends BaseMapper<BpmCategory> {
Page<BpmCategoryVO> page(@Param("page") Page<Object> objectPage, @Param("ew") QueryWrapper<BpmCategoryVO> categoryVOQueryWrapper);
}

View File

@@ -0,0 +1,21 @@
package com.njcn.bpm.mapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.bpm.pojo.po.BpmForm;
import com.njcn.bpm.pojo.vo.BpmFormVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* 动态表单 Mapper
*
* @author 风里雾里
*/
@Mapper
public interface BpmFormMapper extends BaseMapper<BpmForm> {
Page<BpmFormVO> page(@Param("page")Page<Object> objectPage, @Param("ew") QueryWrapper<BpmFormVO> categoryVOQueryWrapper);
}

View File

@@ -0,0 +1,13 @@
package com.njcn.bpm.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.njcn.bpm.pojo.po.BpmProcessDefinitionInfo;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface BpmProcessDefinitionInfoMapper extends BaseMapper<BpmProcessDefinitionInfo> {
}

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.njcn.bpm.mapper.BpmCategoryMapper">
<!--获取流程表单分页列表-->
<select id="page" resultType="BpmCategoryVO">
SELECT
bpm_category.id,
bpm_category.name,
bpm_category.code,
bpm_category.status,
bpm_category.create_time,
bpm_category.remark
FROM bpm_category bpm_category
WHERE ${ew.sqlSegment}
</select>
</mapper>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.njcn.bpm.mapper.BpmFormMapper">
<!--获取流程表单分页列表-->
<select id="page" resultType="BpmFormVO">
SELECT
bpm_form.id,
bpm_form.name,
bpm_form.status,
bpm_form.create_time,
bpm_form.remark
FROM bpm_form bpm_form
WHERE ${ew.sqlSegment}
</select>
</mapper>

View File

@@ -0,0 +1,70 @@
package com.njcn.bpm.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.njcn.bpm.pojo.param.BpmCategoryParam;
import com.njcn.bpm.pojo.po.BpmCategory;
import com.njcn.bpm.pojo.vo.BpmCategoryVO;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import static com.njcn.bpm.utils.CollectionUtils.convertMap;
/**
* BPM 流程分类 Service 接口
*
* @author 芋道源码
*/
public interface IBpmCategoryService extends IService<BpmCategory> {
/**
* 创建流程分类
*
* @return 编号
*/
String createCategory( BpmCategoryParam bpmCategoryParam);
/**
* 更新流程分类
*
*/
void updateCategory( BpmCategoryParam.BpmCategoryUpdateParam updateParam);
/**
* 删除流程分类
*/
void deleteCategory(List<String> ids);
/**
* 获得流程分类分页
*
* @return 流程分类分页
*/
Page<BpmCategoryVO> getCategoryPage(BpmCategoryParam.BpmCategoryQueryParam bpmCategoryQueryParam);
/**
* 获得流程分类 Map基于指定编码
*
* @param codes 编号数组
* @return 流程分类 Map
*/
default Map<String, BpmCategory> getCategoryMap(Collection<String> codes) {
return convertMap(getCategoryListByCode(codes), BpmCategory::getCode);
}
/**
* 获得流程分类列表,基于指定编码
*
* @return 流程分类列表
*/
List<BpmCategory> getCategoryListByCode(Collection<String> codes);
List<BpmCategory> getCategoryList();
}

View File

@@ -0,0 +1,77 @@
package com.njcn.bpm.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.njcn.bpm.pojo.param.BpmFormParam;
import com.njcn.bpm.pojo.po.BpmForm;
import com.njcn.bpm.pojo.vo.BpmFormVO;
import com.njcn.bpm.utils.CollectionUtils;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* 动态表单 Service 接口
*
* @author @风里雾里
*/
public interface IBpmFormService extends IService<BpmForm> {
/**
* 创建动态表单
*
* @return 编号
*/
String createForm(BpmFormParam bpmFormParam);
/**
* 更新动态表单
*
*/
void updateForm(BpmFormParam.BpmFormUpdateParam updateParam);
/**
* 删除动态表单
*
* @param id 编号
*/
void deleteForm(List<String> id);
/**
* 获得动态表单列表
*
* @return 动态表单列表
*/
List<BpmForm> getFormList();
/**
* 获得动态表单列表
*
* @param ids 编号
* @return 动态表单列表
*/
List<BpmForm> getFormList(Collection<String> ids);
/**
* 获得动态表单 Map
*
* @param ids 编号
* @return 动态表单 Map
*/
default Map<String, BpmForm> getFormMap(Collection<String> ids) {
return CollectionUtils.convertMap(this.getFormList(ids), BpmForm::getId);
}
/**
* 获得动态表单分页
*
* @return 动态表单分页
*/
Page<BpmFormVO> getFormPage(BpmFormParam.BpmFormQueryParam bpmFormQueryParam);
}

View File

@@ -0,0 +1,84 @@
package com.njcn.bpm.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.bpm.pojo.param.BpmModelParam;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.repository.Model;
import javax.validation.Valid;
/**
* Flowable流程模型接口
*
* @author yunlongn
*/
public interface IBpmModelService {
/**
* 获得流程模型分页
*
* @return 流程模型分页
*/
Page<Model> getModelPage(BpmModelParam.BpmModelQueryParam bpmModelQueryParam);
/**
* 创建流程模型
*
* @param bpmnXml BPMN XML
* @return 创建的流程模型的编号
*/
String createModel(BpmModelParam bpmModelParam, String bpmnXml);
/**
* 获得流程模块
*
* @param id 编号
* @return 流程模型
*/
Model getModel(String id);
/**
* 获得流程模型的 BPMN XML
*
* @param id 编号
* @return BPMN XML
*/
byte[] getModelBpmnXML(String id);
/**
* 修改流程模型
*
*/
void updateModel(BpmModelParam.BpmModelUpdateParam updateParam);
/**
* 将流程模型,部署成一个流程定义
*
* @param id 编号
*/
void deployModel(String id);
/**
* 删除模型
*
* @param id 编号
*/
void deleteModel(String id);
/**
* 修改模型的状态,实际更新的部署的流程定义的状态
*
* @param id 编号
* @param state 状态
*/
void updateModelState(String id, Integer state);
/**
* 获得流程定义编号对应的 BPMN Model
*
* @param processDefinitionId 流程定义编号
* @return BPMN Model
*/
BpmnModel getBpmnModelByDefinitionId(String processDefinitionId);
}

View File

@@ -0,0 +1,163 @@
package com.njcn.bpm.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.njcn.bpm.pojo.dto.BpmModelMetaInfoRespDTO;
import com.njcn.bpm.pojo.param.BpmProcessDefinitionInfoParam;
import com.njcn.bpm.pojo.po.BpmForm;
import com.njcn.bpm.pojo.po.BpmProcessDefinitionInfo;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ProcessDefinition;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.njcn.bpm.utils.CollectionUtils.convertMap;
/**
* Flowable流程定义接口
*
* @author yunlong.li
* @author ZJQ
* @author 芋道源码
*/
public interface IBpmProcessDefinitionService extends IService<BpmProcessDefinitionInfo> {
/**
* 获得流程定义分页
*
* @return 流程定义 Page
*/
Page<ProcessDefinition> getProcessDefinitionPage(BpmProcessDefinitionInfoParam.BpmProcessDefinitionInfoQueryParam bpmProcessDefinitionInfoQueryParam);
/**
* 获得流程定义列表
*
* @param suspensionState 中断状态
* @return 流程定义列表
*/
List<ProcessDefinition> getProcessDefinitionListBySuspensionState(Integer suspensionState);
/**
* 基于流程模型,创建流程定义
*
* @param model 流程模型
* @param modelMetaInfo 流程模型元信息
* @param bpmnBytes BPMN XML 字节数组
* @param form 表单
* @return 流程编号
*/
String createProcessDefinition(Model model, BpmModelMetaInfoRespDTO modelMetaInfo, byte[] bpmnBytes, BpmForm form);
/**
* 更新流程定义状态
*
* @param id 流程定义的编号
* @param state 状态
*/
void updateProcessDefinitionState(String id, Integer state);
/**
* 获得流程定义对应的 BPMN
*
* @param id 流程定义编号
* @return BPMN
*/
BpmnModel getProcessDefinitionBpmnModel(String id);
/**
* 获得流程定义的信息
*
* @param id 流程定义编号
* @return 流程定义信息
*/
BpmProcessDefinitionInfo getProcessDefinitionInfo(String id);
/**
* 获得流程定义的信息 List
*
* @param ids 流程定义编号数组
* @return 流程额定义信息数组
*/
List<BpmProcessDefinitionInfo> getProcessDefinitionInfoList(Collection<String> ids);
default Map<String, BpmProcessDefinitionInfo> getProcessDefinitionInfoMap(Set<String> ids) {
return convertMap(getProcessDefinitionInfoList(ids), BpmProcessDefinitionInfo::getProcessDefinitionId);
}
/**
* 获得流程定义编号对应的 ProcessDefinition
*
* @param id 流程定义编号
* @return 流程定义
*/
ProcessDefinition getProcessDefinition(String id);
/**
* 获得 ids 对应的 ProcessDefinition 数组
*
* @param ids 编号的数组
* @return 流程定义的数组
*/
List<ProcessDefinition> getProcessDefinitionList(Set<String> ids);
default Map<String, ProcessDefinition> getProcessDefinitionMap(Set<String> ids) {
return convertMap(getProcessDefinitionList(ids), ProcessDefinition::getId);
}
/**
* 获得 deploymentId 对应的 ProcessDefinition
*
* @param deploymentId 部署编号
* @return 流程定义
*/
ProcessDefinition getProcessDefinitionByDeploymentId(String deploymentId);
/**
* 获得 deploymentIds 对应的 ProcessDefinition 数组
*
* @param deploymentIds 部署编号的数组
* @return 流程定义的数组
*/
List<ProcessDefinition> getProcessDefinitionListByDeploymentIds(Set<String> deploymentIds);
/**
* 获得流程定义标识对应的激活的流程定义
*
* @param key 流程定义的标识
* @return 流程定义
*/
ProcessDefinition getActiveProcessDefinition(String key);
/**
* 获得 ids 对应的 Deployment Map
*
* @param ids 部署编号的数组
* @return 流程部署 Map
*/
default Map<String, Deployment> getDeploymentMap(Set<String> ids) {
return convertMap(getDeploymentList(ids), Deployment::getId);
}
/**
* 获得 ids 对应的 Deployment 数组
*
* @param ids 部署编号的数组
* @return 流程部署的数组
*/
List<Deployment> getDeploymentList(Set<String> ids);
/**
* 获得 id 对应的 Deployment
*
* @param id 部署编号
* @return 流程部署
*/
Deployment getDeployment(String id);
}

View File

@@ -0,0 +1,143 @@
package com.njcn.bpm.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.njcn.bpm.enums.BpmResponseEnum;
import com.njcn.bpm.mapper.BpmCategoryMapper;
import com.njcn.bpm.pojo.param.BpmCategoryParam;
import com.njcn.bpm.pojo.po.BpmCategory;
import com.njcn.bpm.pojo.po.BpmForm;
import com.njcn.bpm.pojo.vo.BpmCategoryVO;
import com.njcn.bpm.pojo.vo.BpmFormVO;
import com.njcn.bpm.service.IBpmCategoryService;
import com.njcn.bpm.utils.BeanUtils;
import com.njcn.common.pojo.enums.common.DataStateEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.web.factory.PageFactory;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* BPM 流程分类 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class BpmCategoryServiceImpl extends ServiceImpl<BpmCategoryMapper, BpmCategory> implements IBpmCategoryService {
@Override
public String createCategory(BpmCategoryParam bpmCategoryParam) {
// 校验唯一
checkCategoryName(bpmCategoryParam, false);
checkCategoryCode(bpmCategoryParam, false);
// 插入
BpmCategory category = BeanUtils.toBean(bpmCategoryParam, BpmCategory.class);
this.baseMapper.insert(category);
return category.getId();
}
@Override
public void updateCategory(BpmCategoryParam.BpmCategoryUpdateParam updateParam) {
// 校验唯一
checkCategoryName(updateParam, true);
checkCategoryCode(updateParam, true);
// 更新
BpmCategory updateObj = BeanUtils.toBean(updateParam, BpmCategory.class);
this.baseMapper.updateById(updateObj);
}
@Override
public void deleteCategory(List<String> ids) {
this.lambdaUpdate().set(BpmCategory::getState, DataStateEnum.DELETED.getCode())
.in(BpmCategory::getId, ids)
.update();
}
@Override
public Page<BpmCategoryVO> getCategoryPage(BpmCategoryParam.BpmCategoryQueryParam bpmCategoryQueryParam) {
QueryWrapper<BpmCategoryVO> categoryVOQueryWrapper = new QueryWrapper<>();
if (StrUtil.isNotBlank(bpmCategoryQueryParam.getName())) {
categoryVOQueryWrapper.like("bpm_category.name", bpmCategoryQueryParam.getName());
}
if (StrUtil.isNotBlank(bpmCategoryQueryParam.getCode())) {
categoryVOQueryWrapper.like("bpm_category.name", bpmCategoryQueryParam.getCode());
}
categoryVOQueryWrapper.eq("bpm_category.state", DataStateEnum.ENABLE.getCode());
categoryVOQueryWrapper.orderByDesc("bpm_category.update_time");
return this.baseMapper.page(new Page<>(PageFactory.getPageNum(bpmCategoryQueryParam), PageFactory.getPageSize(bpmCategoryQueryParam)), categoryVOQueryWrapper);
}
@Override
public List<BpmCategory> getCategoryListByCode(Collection<String> codes) {
if (CollUtil.isEmpty(codes)) {
return Collections.emptyList();
}
LambdaQueryWrapper<BpmCategory> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.in(BpmCategory::getCode,codes);
return this.baseMapper.selectList(lambdaQueryWrapper);
}
@Override
public List<BpmCategory> getCategoryList() {
LambdaQueryWrapper<BpmCategory> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.select(BpmCategory::getId,BpmCategory::getName)
.eq(BpmCategory::getState,DataStateEnum.ENABLE.getCode());
return this.baseMapper.selectList(lambdaQueryWrapper);
}
private void checkCategoryName(BpmCategoryParam bpmCategoryParam, boolean isExcludeSelf) {
LambdaQueryWrapper<BpmCategory> categoryLambdaQueryWrapper = new LambdaQueryWrapper<>();
//判断流程表单的名称
categoryLambdaQueryWrapper
.eq(BpmCategory::getName, bpmCategoryParam.getName())
.eq(BpmCategory::getState, DataStateEnum.ENABLE.getCode());
//更新的时候,需排除当前记录
if (isExcludeSelf) {
if (bpmCategoryParam instanceof BpmCategoryParam.BpmCategoryUpdateParam) {
categoryLambdaQueryWrapper.ne(BpmCategory::getId, ((BpmCategoryParam.BpmCategoryUpdateParam) bpmCategoryParam).getId());
}
}
int nameCountByAccount = this.count(categoryLambdaQueryWrapper);
//大于等于1个则表示重复
if (nameCountByAccount >= 1) {
throw new BusinessException(BpmResponseEnum.REPEAT_CATEGORY_NAME_FORM);
}
}
private void checkCategoryCode(BpmCategoryParam bpmCategoryParam, boolean isExcludeSelf) {
LambdaQueryWrapper<BpmCategory> categoryLambdaQueryWrapper = new LambdaQueryWrapper<>();
//判断流程表单的名称
categoryLambdaQueryWrapper
.eq(BpmCategory::getCode, bpmCategoryParam.getCode())
.eq(BpmCategory::getState, DataStateEnum.ENABLE.getCode());
//更新的时候,需排除当前记录
if (isExcludeSelf) {
if (bpmCategoryParam instanceof BpmCategoryParam.BpmCategoryUpdateParam) {
categoryLambdaQueryWrapper.ne(BpmCategory::getId, ((BpmCategoryParam.BpmCategoryUpdateParam) bpmCategoryParam).getId());
}
}
int nameCountByAccount = this.count(categoryLambdaQueryWrapper);
//大于等于1个则表示重复
if (nameCountByAccount >= 1) {
throw new BusinessException(BpmResponseEnum.REPEAT_CATEGORY_NAME_FORM);
}
}
}

View File

@@ -0,0 +1,139 @@
package com.njcn.bpm.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.njcn.bpm.enums.BpmResponseEnum;
import com.njcn.bpm.mapper.BpmFormMapper;
import com.njcn.bpm.pojo.param.BpmFormParam;
import com.njcn.bpm.pojo.po.BpmForm;
import com.njcn.bpm.pojo.vo.BpmFormVO;
import com.njcn.bpm.service.IBpmFormService;
import com.njcn.bpm.utils.BeanUtils;
import com.njcn.common.pojo.enums.common.DataStateEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.web.factory.PageFactory;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.*;
/**
* 动态表单 Service 实现类
*
* @author 风里雾里
*/
@Service
@Validated
public class BpmFormServiceImpl extends ServiceImpl<BpmFormMapper, BpmForm> implements IBpmFormService {
@Override
public Page<BpmFormVO> getFormPage(BpmFormParam.BpmFormQueryParam bpmFormQueryParam) {
QueryWrapper<BpmFormVO> bpmFormVOQueryWrapper = new QueryWrapper<>();
if (StrUtil.isNotBlank(bpmFormQueryParam.getName())) {
bpmFormVOQueryWrapper.like("bpm_form.name", bpmFormQueryParam.getName());
}
bpmFormVOQueryWrapper.eq("bpm_form.state", DataStateEnum.ENABLE.getCode());
bpmFormVOQueryWrapper.orderByDesc("bpm_form.update_time");
return this.baseMapper.page(new Page<>(PageFactory.getPageNum(bpmFormQueryParam), PageFactory.getPageSize(bpmFormQueryParam)), bpmFormVOQueryWrapper);
}
@Override
public String createForm(BpmFormParam bpmFormParam) {
// this.validateFields(createReqVO.getFields());
checkFormName(bpmFormParam, false);
// 插入
BpmForm form = BeanUtils.toBean(bpmFormParam, BpmForm.class);
this.baseMapper.insert(form);
// 返回
return form.getId();
}
private void checkFormName(BpmFormParam bpmFormParam, boolean isExcludeSelf) {
LambdaQueryWrapper<BpmForm> categoryLambdaQueryWrapper = new LambdaQueryWrapper<>();
//判断流程表单的名称
categoryLambdaQueryWrapper
.eq(BpmForm::getName, bpmFormParam.getName())
.eq(BpmForm::getState, DataStateEnum.ENABLE.getCode());
//更新的时候,需排除当前记录
if (isExcludeSelf) {
if (bpmFormParam instanceof BpmFormParam.BpmFormUpdateParam) {
categoryLambdaQueryWrapper.ne(BpmForm::getId, ((BpmFormParam.BpmFormUpdateParam) bpmFormParam).getId());
}
}
int nameCountByAccount = this.count(categoryLambdaQueryWrapper);
//大于等于1个则表示重复
if (nameCountByAccount >= 1) {
throw new BusinessException(BpmResponseEnum.REPEAT_NAME_FORM);
}
}
@Override
public void updateForm(BpmFormParam.BpmFormUpdateParam updateParam) {
checkFormName(updateParam, true);
BpmForm updateObj = BeanUtils.toBean(updateParam, BpmForm.class);
this.updateById(updateObj);
}
@Override
public void deleteForm(List<String> ids) {
this.lambdaUpdate().set(BpmForm::getState, DataStateEnum.DELETED.getCode())
.in(BpmForm::getId, ids)
.update();
}
// private void validateFormExists(Long id) {
// if (this.baseMapper.selectById(id) == null) {
// throw exception(ErrorCodeConstants.FORM_NOT_EXISTS);
// }
// }
@Override
public List<BpmForm> getFormList() {
LambdaQueryWrapper<BpmForm> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.select(BpmForm::getId,BpmForm::getName)
.eq(BpmForm::getState,DataStateEnum.ENABLE.getCode());
return this.baseMapper.selectList(lambdaQueryWrapper);
}
@Override
public List<BpmForm> getFormList(Collection<String> ids) {
if (CollUtil.isEmpty(ids)) {
return Collections.emptyList();
}
return this.baseMapper.selectBatchIds(ids);
}
/**
* 校验 Field避免 field 重复
*
* @param fields field 数组
*/
private void validateFields(List<String> fields) {
if (true) { // TODO 芋艿:兼容 Vue3 工作流:因为采用了新的表单设计器,所以暂时不校验
return;
}
// Map<String, String> fieldMap = new HashMap<>(); // key 是 vModelvalue 是 label
// for (String field : fields) {
// BpmFormFieldRespDTO fieldDTO = JsonUtils.parseObject(field, BpmFormFieldRespDTO.class);
// Assert.notNull(fieldDTO);
// String oldLabel = fieldMap.put(fieldDTO.getVModel(), fieldDTO.getLabel());
// // 如果不存在,则直接返回
// if (oldLabel == null) {
// continue;
// }
// // 如果存在,则报错
// throw exception(ErrorCodeConstants.FORM_FIELD_REPEAT, oldLabel, fieldDTO.getLabel(), fieldDTO.getVModel());
// }
}
}

View File

@@ -0,0 +1,283 @@
package com.njcn.bpm.service.impl;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.bpm.enums.BpmModelFormTypeEnum;
import com.njcn.bpm.enums.BpmResponseEnum;
import com.njcn.bpm.pojo.dto.BpmModelMetaInfoRespDTO;
import com.njcn.bpm.pojo.param.BpmModelParam;
import com.njcn.bpm.pojo.po.BpmForm;
import com.njcn.bpm.service.IBpmFormService;
import com.njcn.bpm.service.IBpmModelService;
import com.njcn.bpm.service.IBpmProcessDefinitionService;
import com.njcn.bpm.strategy.BpmTaskCandidateInvoker;
import com.njcn.bpm.utils.*;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.web.factory.PageFactory;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.StartEvent;
import org.flowable.bpmn.model.UserTask;
import org.flowable.common.engine.impl.db.SuspensionState;
import org.flowable.engine.ProcessEngineConfiguration;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ModelQuery;
import org.flowable.engine.repository.ProcessDefinition;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import java.util.Objects;
/**
* Flowable流程模型实现
* 主要进行 Flowable {@link Model} 的维护
*
* @author yunlongn
* @author 芋道源码
* @author jason
*/
@Service
@Validated
@Slf4j
public class BpmModelServiceImpl implements IBpmModelService {
@Resource
private RepositoryService repositoryService;
@Resource
private IBpmProcessDefinitionService processDefinitionService;
@Resource
private IBpmFormService bpmFormService;
@Resource
private BpmTaskCandidateInvoker taskCandidateInvoker;
@Override
public Page<Model> getModelPage(BpmModelParam.BpmModelQueryParam bpmModelQueryParam) {
ModelQuery modelQuery = repositoryService.createModelQuery();
if (StrUtil.isNotBlank(bpmModelQueryParam.getKey())) {
modelQuery.modelKey(bpmModelQueryParam.getKey());
}
if (StrUtil.isNotBlank(bpmModelQueryParam.getName())) {
modelQuery.modelNameLike("%" + bpmModelQueryParam.getName() + "%"); // 模糊匹配
}
if (StrUtil.isNotBlank(bpmModelQueryParam.getCategory())) {
modelQuery.modelCategory(bpmModelQueryParam.getCategory());
}
// 执行查询
long count = modelQuery.count();
if (count == 0) {
return new Page<>();
}
int offset = PageFactory.getPageSize(bpmModelQueryParam) * (PageFactory.getPageNum(bpmModelQueryParam) - 1);
List<Model> models = modelQuery
.modelTenantId(ProcessEngineConfiguration.NO_TENANT_ID)
.orderByCreateTime().desc()
.listPage(offset, PageFactory.getPageSize(bpmModelQueryParam));
Page<Model> page = new Page<>();
page.setRecords(models);
page.setTotal(count);
page.setSize(PageFactory.getPageSize(bpmModelQueryParam));
page.setCurrent(PageFactory.getPageNum(bpmModelQueryParam));
return page;
}
@Override
@Transactional(rollbackFor = Exception.class)
public String createModel(BpmModelParam bpmModelParam, String bpmnXml) {
if (!ValidationUtils.isXmlNCName(bpmModelParam.getKey())) {
throw new BusinessException(BpmResponseEnum.BPM_XML_ERROR);
}
// 校验流程标识已经存在
Model keyModel = getModelByKey(bpmModelParam.getKey());
if (keyModel != null) {
throw new BusinessException(BpmResponseEnum.BPM_MODEL_REPEAT);
}
// 创建流程定义
Model model = repositoryService.newModel();
BpmModelConvert.INSTANCE.copyToCreateModel(model, bpmModelParam);
model.setTenantId(FlowableUtils.getTenantId());
// 保存流程定义
repositoryService.saveModel(model);
// 保存 BPMN XML
saveModelBpmnXml(model, bpmnXml);
return model.getId();
}
@Override
@Transactional(rollbackFor = Exception.class) // 因为进行多个操作,所以开启事务
public void updateModel(BpmModelParam.BpmModelUpdateParam updateParam) {
// 校验流程模型存在
Model model = getModel(updateParam.getId());
if (model == null) {
throw new BusinessException(BpmResponseEnum.BPM_MODEL_NOT_EXIST);
}
// 修改流程定义
BpmModelConvert.INSTANCE.copyToUpdateModel(model, updateParam);
// 更新模型
repositoryService.saveModel(model);
// 更新 BPMN XML
saveModelBpmnXml(model, updateParam.getBpmnXml());
}
@Override
@Transactional(rollbackFor = Exception.class) // 因为进行多个操作,所以开启事务
public void deployModel(String id) {
// 1.1 校验流程模型存在
Model model = getModel(id);
if (ObjectUtils.isEmpty(model)) {
throw new BusinessException(BpmResponseEnum.BPM_MODEL_NOT_EXIST);
}
// 1.2 校验流程图
byte[] bpmnBytes = getModelBpmnXML(model.getId());
validateBpmnXml(bpmnBytes);
// 1.3 校验表单已配
BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class);
BpmForm form = validateFormConfig(metaInfo);
// 1.4 校验任务分配规则已配置
taskCandidateInvoker.validateBpmnConfig(bpmnBytes);
// 2.1 创建流程定义
String definitionId = processDefinitionService.createProcessDefinition(model, metaInfo, bpmnBytes, form);
// 2.2 将老的流程定义进行挂起。也就是说,只有最新部署的流程定义,才可以发起任务。
updateProcessDefinitionSuspended(model.getDeploymentId());
// 2.3 更新 model 的 deploymentId进行关联
ProcessDefinition definition = processDefinitionService.getProcessDefinition(definitionId);
model.setDeploymentId(definition.getDeploymentId());
repositoryService.saveModel(model);
}
private void validateBpmnXml(byte[] bpmnBytes) {
BpmnModel bpmnModel = BpmnModelUtils.getBpmnModel(bpmnBytes);
if (bpmnModel == null) {
throw new BusinessException(BpmResponseEnum.BPM_MODEL_NOT_EXIST);
}
// 1. 没有 StartEvent
StartEvent startEvent = BpmnModelUtils.getStartEvent(bpmnModel);
if (startEvent == null) {
throw new BusinessException(BpmResponseEnum.BPM_START_EVENT_NOT_EXIST);
}
// 2. 校验 UserTask 的 name 都配置了
List<UserTask> userTasks = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class);
UserTask userTask1 = userTasks.get(0);
userTask1.getName();
userTasks.forEach(userTask -> {
if (StrUtil.isEmpty(userTask.getName())) {
throw new BusinessException(BpmResponseEnum.MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS);
}
});
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteModel(String id) {
// 校验流程模型存在
Model model = getModel(id);
if (model == null) {
throw new BusinessException(BpmResponseEnum.BPM_MODEL_NOT_EXIST);
}
// 执行删除
repositoryService.deleteModel(id);
// 禁用流程定义
updateProcessDefinitionSuspended(model.getDeploymentId());
}
@Override
public void updateModelState(String id, Integer state) {
// 1.1 校验流程模型存在
Model model = getModel(id);
if (model == null) {
throw new BusinessException(BpmResponseEnum.BPM_MODEL_NOT_EXIST);
}
// 1.2 校验流程定义存在
ProcessDefinition definition = processDefinitionService.getProcessDefinitionByDeploymentId(model.getDeploymentId());
if (definition == null) {
throw new BusinessException(BpmResponseEnum.PROCESS_DEFINITION_NOT_EXISTS);
}
// 2. 更新状态
processDefinitionService.updateProcessDefinitionState(definition.getId(), state);
}
@Override
public BpmnModel getBpmnModelByDefinitionId(String processDefinitionId) {
return repositoryService.getBpmnModel(processDefinitionId);
}
/**
* 校验流程表单已配置
*
* @param metaInfo 流程模型元数据
* @return 表单配置
*/
private BpmForm validateFormConfig(BpmModelMetaInfoRespDTO metaInfo) {
if (metaInfo == null || metaInfo.getFormType() == null) {
throw new BusinessException(BpmResponseEnum.MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG);
}
// 校验表单存在
if (Objects.equals(metaInfo.getFormType(), BpmModelFormTypeEnum.NORMAL.getType())) {
if (metaInfo.getFormId() == null) {
throw new BusinessException(BpmResponseEnum.MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG);
}
BpmForm form = bpmFormService.getById(metaInfo.getFormId());
if (form == null) {
throw new BusinessException(BpmResponseEnum.FORM_NOT_EXISTS);
}
return form;
} else {
if (StrUtil.isEmpty(metaInfo.getFormCustomCreatePath()) || StrUtil.isEmpty(metaInfo.getFormCustomViewPath())) {
throw new BusinessException(BpmResponseEnum.MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG);
}
return null;
}
}
private void saveModelBpmnXml(Model model, String bpmnXml) {
if (StrUtil.isEmpty(bpmnXml)) {
return;
}
repositoryService.addModelEditorSource(model.getId(), StrUtil.utf8Bytes(bpmnXml));
}
/**
* 挂起 deploymentId 对应的流程定义
* <p>
* 注意:这里一个 deploymentId 只关联一个流程定义
*
* @param deploymentId 流程发布Id
*/
private void updateProcessDefinitionSuspended(String deploymentId) {
if (StrUtil.isEmpty(deploymentId)) {
return;
}
ProcessDefinition oldDefinition = processDefinitionService.getProcessDefinitionByDeploymentId(deploymentId);
if (oldDefinition == null) {
return;
}
processDefinitionService.updateProcessDefinitionState(oldDefinition.getId(), SuspensionState.SUSPENDED.getStateCode());
}
private Model getModelByKey(String key) {
return repositoryService.createModelQuery().modelKey(key).singleResult();
}
@Override
public Model getModel(String id) {
return repositoryService.getModel(id);
}
@Override
public byte[] getModelBpmnXML(String id) {
return repositoryService.getModelEditorSource(id);
}
}

View File

@@ -0,0 +1,218 @@
package com.njcn.bpm.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.njcn.bpm.enums.BpmnModelConstants;
import com.njcn.bpm.mapper.BpmCategoryMapper;
import com.njcn.bpm.mapper.BpmProcessDefinitionInfoMapper;
import com.njcn.bpm.pojo.dto.BpmModelMetaInfoRespDTO;
import com.njcn.bpm.pojo.param.BpmProcessDefinitionInfoParam;
import com.njcn.bpm.pojo.po.BpmForm;
import com.njcn.bpm.pojo.po.BpmProcessDefinitionInfo;
import com.njcn.bpm.service.IBpmProcessDefinitionService;
import com.njcn.bpm.utils.BeanUtils;
import com.njcn.bpm.utils.FlowableUtils;
import com.njcn.common.pojo.enums.common.DataStateEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.web.factory.PageFactory;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.common.engine.impl.db.SuspensionState;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.repository.ProcessDefinitionQuery;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.*;
import static com.njcn.bpm.utils.CollectionUtils.addIfNotNull;
import static java.util.Collections.emptyList;
/**
* 流程定义实现
* 主要进行 Flowable {@link ProcessDefinition} 和 {@link Deployment} 的维护
*
* @author yunlongn
* @author ZJQ
* @author 芋道源码
*/
@Service
@Validated
@Slf4j
public class BpmProcessDefinitionServiceImpl extends ServiceImpl<BpmProcessDefinitionInfoMapper, BpmProcessDefinitionInfo> implements IBpmProcessDefinitionService {
@Resource
private RepositoryService repositoryService;
@Override
public Page<ProcessDefinition> getProcessDefinitionPage(BpmProcessDefinitionInfoParam.BpmProcessDefinitionInfoQueryParam bpmProcessDefinitionInfoQueryParam) {
ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery();
if (StrUtil.isNotBlank(bpmProcessDefinitionInfoQueryParam.getKey())) {
query.processDefinitionKey(bpmProcessDefinitionInfoQueryParam.getKey());
}
// 执行查询
long count = query.count();
if (count == 0) {
return new Page<>();
}
int offset = PageFactory.getPageSize(bpmProcessDefinitionInfoQueryParam) * (PageFactory.getPageNum(bpmProcessDefinitionInfoQueryParam) - 1);
List<ProcessDefinition> processDefinitionList = query.orderByProcessDefinitionVersion().desc()
.listPage(offset, PageFactory.getPageSize(bpmProcessDefinitionInfoQueryParam));
Page<ProcessDefinition> page = new Page<>();
page.setRecords(processDefinitionList);
page.setTotal(count);
page.setSize(PageFactory.getPageSize(bpmProcessDefinitionInfoQueryParam));
page.setCurrent(PageFactory.getPageNum(bpmProcessDefinitionInfoQueryParam));
return page;
}
@Override
public ProcessDefinition getProcessDefinition(String id) {
return repositoryService.getProcessDefinition(id);
}
@Override
public List<ProcessDefinition> getProcessDefinitionList(Set<String> ids) {
return repositoryService.createProcessDefinitionQuery().processDefinitionIds(ids).list();
}
@Override
public ProcessDefinition getProcessDefinitionByDeploymentId(String deploymentId) {
if (StrUtil.isEmpty(deploymentId)) {
return null;
}
return repositoryService.createProcessDefinitionQuery().deploymentId(deploymentId).singleResult();
}
@Override
public List<ProcessDefinition> getProcessDefinitionListByDeploymentIds(Set<String> deploymentIds) {
if (CollUtil.isEmpty(deploymentIds)) {
return emptyList();
}
return repositoryService.createProcessDefinitionQuery().deploymentIds(deploymentIds).list();
}
@Override
public ProcessDefinition getActiveProcessDefinition(String key) {
return repositoryService.createProcessDefinitionQuery().processDefinitionKey(key).active().singleResult();
}
@Override
public List<Deployment> getDeploymentList(Set<String> ids) {
if (CollUtil.isEmpty(ids)) {
return emptyList();
}
List<Deployment> list = new ArrayList<>(ids.size());
for (String id : ids) {
addIfNotNull(list, getDeployment(id));
}
return list;
}
@Override
public Deployment getDeployment(String id) {
if (StrUtil.isEmpty(id)) {
return null;
}
return repositoryService.createDeploymentQuery().deploymentId(id).singleResult();
}
@Override
public String createProcessDefinition(Model model, BpmModelMetaInfoRespDTO modelMetaInfo,
byte[] bpmnBytes, BpmForm form) {
// 创建 Deployment 部署
Deployment deploy = repositoryService.createDeployment()
.key(model.getKey()).name(model.getName()).category(model.getCategory())
.addBytes(model.getKey() + BpmnModelConstants.BPMN_FILE_SUFFIX, bpmnBytes)
.tenantId(FlowableUtils.getTenantId())
.disableSchemaValidation() // 禁用 XML Schema 验证,因为有自定义的属性
.deploy();
// 设置 ProcessDefinition 的 category 分类
ProcessDefinition definition = repositoryService.createProcessDefinitionQuery()
.deploymentId(deploy.getId()).singleResult();
repositoryService.setProcessDefinitionCategory(definition.getId(), model.getCategory());
// 注意 1ProcessDefinition 的 key 和 name 是通过 BPMN 中的 <bpmn2:process /> 的 id 和 name 决定
// 注意 2目前该项目的设计上需要保证 Model、Deployment、ProcessDefinition 使用相同的 key保证关联性。
// 否则,会导致 ProcessDefinition 的分页无法查询到。
if (!Objects.equals(definition.getKey(), model.getKey())) {
throw new BusinessException("流程定义的标识期望是(" + model.getKey() + "),当前是(" + definition.getKey() + "),请修改 BPMN 流程图");
}
if (!Objects.equals(definition.getName(), model.getName())) {
throw new BusinessException("流程定义的名字期望是(" + model.getName() + "),当前是(" + definition.getName() + "),请修改 BPMN 流程图");
}
// 插入拓展表
BpmProcessDefinitionInfo definitionDO = BeanUtils.toBean(modelMetaInfo, BpmProcessDefinitionInfo.class);
definitionDO.setModelId(model.getId());
definitionDO.setProcessDefinitionId(definition.getId());
if (form != null) {
definitionDO.setFormFields(form.getFields());
definitionDO.setFormConf(form.getConf());
}
this.baseMapper.insert(definitionDO);
return definition.getId();
}
@Override
public void updateProcessDefinitionState(String id, Integer state) {
// 激活
if (Objects.equals(SuspensionState.ACTIVE.getStateCode(), state)) {
repositoryService.activateProcessDefinitionById(id, false, null);
return;
}
// 挂起
if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), state)) {
// suspendProcessInstances = false进行中的任务不进行挂起。
// 原因:只要新的流程不允许发起即可,老流程继续可以执行。
repositoryService.suspendProcessDefinitionById(id, false, null);
return;
}
log.error("[updateProcessDefinitionState][流程定义({}) 修改未知状态({})]", id, state);
}
@Override
public BpmnModel getProcessDefinitionBpmnModel(String id) {
return repositoryService.getBpmnModel(id);
}
@Override
public BpmProcessDefinitionInfo getProcessDefinitionInfo(String id) {
LambdaQueryWrapper<BpmProcessDefinitionInfo> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(BpmProcessDefinitionInfo::getProcessDefinitionId, id);
return this.baseMapper.selectOne(lambdaQueryWrapper);
}
@Override
public List<BpmProcessDefinitionInfo> getProcessDefinitionInfoList(Collection<String> ids) {
LambdaQueryWrapper<BpmProcessDefinitionInfo> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(BpmProcessDefinitionInfo::getProcessDefinitionId, ids)
.eq(BpmProcessDefinitionInfo::getState, DataStateEnum.ENABLE.getCode());
return this.baseMapper.selectList(lambdaQueryWrapper);
}
@Override
public List<ProcessDefinition> getProcessDefinitionListBySuspensionState(Integer suspensionState) {
// 拼接查询条件
ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery();
if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), suspensionState)) {
query.suspended();
} else if (Objects.equals(SuspensionState.ACTIVE.getStateCode(), suspensionState)) {
query.active();
}
// 执行查询
query.processDefinitionTenantId(FlowableUtils.getTenantId());
return query.list();
}
}

View File

@@ -0,0 +1,139 @@
package com.njcn.bpm.service.task;
import org.flowable.engine.delegate.event.FlowableCancelledEvent;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.runtime.ProcessInstance;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.njcn.bpm.utils.CollectionUtils.convertMap;
/**
* 流程实例 Service 接口
*
* @author 芋道源码
*/
public interface IBpmProcessInstanceService {
/**
* 获得流程实例
*
* @param id 流程实例的编号
* @return 流程实例
*/
ProcessInstance getProcessInstance(String id);
//
// /**
// * 获得流程实例列表
// *
// * @param ids 流程实例的编号集合
// * @return 流程实例列表
// */
// List<ProcessInstance> getProcessInstances(Set<String> ids);
//
// /**
// * 获得流程实例 Map
// *
// * @param ids 流程实例的编号集合
// * @return 流程实例列表 Map
// */
// default Map<String, ProcessInstance> getProcessInstanceMap(Set<String> ids) {
// return convertMap(getProcessInstances(ids), ProcessInstance::getProcessInstanceId);
// }
//
// /**
// * 获得历史的流程实例
// *
// * @param id 流程实例的编号
// * @return 历史的流程实例
// */
// HistoricProcessInstance getHistoricProcessInstance(String id);
//
// /**
// * 获得历史的流程实例列表
// *
// * @param ids 流程实例的编号集合
// * @return 历史的流程实例列表
// */
// List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids);
//
// /**
// * 获得历史的流程实例 Map
// *
// * @param ids 流程实例的编号集合
// * @return 历史的流程实例列表 Map
// */
// default Map<String, HistoricProcessInstance> getHistoricProcessInstanceMap(Set<String> ids) {
// return convertMap(getHistoricProcessInstances(ids), HistoricProcessInstance::getId);
// }
//
// /**
// * 获得流程实例的分页
// *
// * @param userId 用户编号
// * @param pageReqVO 分页请求
// * @return 流程实例的分页
// */
// PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, @Valid BpmProcessInstancePageReqVO pageReqVO);
//
// /**
// * 创建流程实例(提供给前端)
// *
// * @param userId 用户编号
// * @param createReqVO 创建信息
// * @return 实例的编号
// */
// String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO);
//
// /**
// * 创建流程实例(提供给内部)
// *
// * @param userId 用户编号
// * @param createReqDTO 创建信息
// * @return 实例的编号
// */
// String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO);
//
// /**
// * 发起人取消流程实例
// *
// * @param userId 用户编号
// * @param cancelReqVO 取消信息
// */
// void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO);
//
// /**
// * 管理员取消流程实例
// *
// * @param userId 用户编号
// * @param cancelReqVO 取消信息
// */
// void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO);
//
// /**
// * 更新 ProcessInstance 拓展记录为取消
// *
// * @param event 流程取消事件
// */
// void updateProcessInstanceWhenCancel(FlowableCancelledEvent event);
//
// /**
// * 更新 ProcessInstance 拓展记录为完成
// *
// * @param instance 流程任务
// */
// void updateProcessInstanceWhenApprove(ProcessInstance instance);
//
// /**
// * 更新 ProcessInstance 拓展记录为不通过
// *
// * @param id 流程编号
// * @param reason 理由。例如说,审批不通过时,需要传递该值
// */
// void updateProcessInstanceReject(String id, String reason);
}

View File

@@ -0,0 +1,292 @@
package com.njcn.bpm.service.task.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.njcn.bpm.service.task.IBpmProcessInstanceService;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.delegate.event.FlowableCancelledEvent;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.*;
/**
* 流程实例 Service 实现类
*
* ProcessDefinition & ProcessInstance & Execution & Task 的关系:
* 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" />
*
* HistoricProcessInstance & ProcessInstance 的关系:
* 1. <a href=" https://my.oschina.net/843294669/blog/71902" />
*
* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例
*
* @author 芋道源码
*/
@Service
@Validated
@Slf4j
public class BpmProcessInstanceServiceImpl implements IBpmProcessInstanceService {
@Resource
private RuntimeService runtimeService;
@Resource
private HistoryService historyService;
// @Resource
// private BpmProcessDefinitionService processDefinitionService;
// @Resource
// private BpmMessageService messageService;
//
// @Resource
// private AdminUserApi adminUserApi;
//
// @Resource
// private BpmProcessInstanceEventPublisher processInstanceEventPublisher;
@Override
public ProcessInstance getProcessInstance(String id) {
return runtimeService.createProcessInstanceQuery()
.includeProcessVariables()
.processInstanceId(id)
.singleResult();
}
//
// @Override
// public List<ProcessInstance> getProcessInstances(Set<String> ids) {
// return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list();
// }
//
// @Override
// public HistoricProcessInstance getHistoricProcessInstance(String id) {
// return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult();
// }
//
// @Override
// public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) {
// return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list();
// }
//
// @Override
// public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId,
// BpmProcessInstancePageReqVO pageReqVO) {
// // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页
// HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery()
// .includeProcessVariables()
// .processInstanceTenantId(FlowableUtils.getTenantId())
// .orderByProcessInstanceStartTime().desc();
// if (userId != null) { // 【我的流程】菜单时,需要传递该字段
// processInstanceQuery.startedBy(String.valueOf(userId));
// } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段
// processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId()));
// }
// if (StrUtil.isNotEmpty(pageReqVO.getName())) {
// processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%");
// }
// if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) {
// processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%");
// }
// if (StrUtil.isNotEmpty(pageReqVO.getCategory())) {
// processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory());
// }
// if (pageReqVO.getStatus() != null) {
// processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus());
// }
// if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) {
// processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0]));
// processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1]));
// }
// // 查询数量
// long processInstanceCount = processInstanceQuery.count();
// if (processInstanceCount == 0) {
// return PageResult.empty(processInstanceCount);
// }
// // 查询列表
// List<HistoricProcessInstance> processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize());
// return new PageResult<>(processInstanceList, processInstanceCount);
// }
//
// @Override
// @Transactional(rollbackFor = Exception.class)
// public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) {
// // 获得流程定义
// ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId());
// // 发起流程
// return createProcessInstance0(userId, definition, createReqVO.getVariables(), null,
// createReqVO.getStartUserSelectAssignees());
// }
//
// @Override
// public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) {
// // 获得流程定义
// ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey());
// // 发起流程
// return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(),
// createReqDTO.getStartUserSelectAssignees());
// }
//
// private String createProcessInstance0(Long userId, ProcessDefinition definition,
// Map<String, Object> variables, String businessKey,
// Map<String, List<Long>> startUserSelectAssignees) {
// // 1.1 校验流程定义
// if (definition == null) {
// throw exception(PROCESS_DEFINITION_NOT_EXISTS);
// }
// if (definition.isSuspended()) {
// throw exception(PROCESS_DEFINITION_IS_SUSPENDED);
// }
// // 1.2 校验发起人自选审批人
// validateStartUserSelectAssignees(definition, startUserSelectAssignees);
//
// // 2. 创建流程实例
// if (variables == null) {
// variables = new HashMap<>();
// }
// FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用
// variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中
// BpmProcessInstanceStatusEnum.RUNNING.getStatus());
// if (CollUtil.isNotEmpty(startUserSelectAssignees)) {
// variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees);
// }
// ProcessInstance instance = runtimeService.createProcessInstanceBuilder()
// .processDefinitionId(definition.getId())
// .businessKey(businessKey)
// .name(definition.getName().trim())
// .variables(variables)
// .start();
// return instance.getId();
// }
//
// private void validateStartUserSelectAssignees(ProcessDefinition definition, Map<String, List<Long>> startUserSelectAssignees) {
// // 1. 获得发起人自选审批人的 UserTask 列表
// BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId());
// List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel);
// if (CollUtil.isEmpty(userTaskList)) {
// return;
// }
//
// // 2. 校验发起人自选审批人的 UserTask 是否都配置了
// userTaskList.forEach(userTask -> {
// List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null;
// if (CollUtil.isEmpty(assignees)) {
// throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName());
// }
// Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assignees);
// assignees.forEach(assignee -> {
// if (userMap.get(assignee) == null) {
// throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee);
// }
// });
// });
// }
//
// @Override
// public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) {
// // 1.1 校验流程实例存在
// ProcessInstance instance = getProcessInstance(cancelReqVO.getId());
// if (instance == null) {
// throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS);
// }
// // 1.2 只能取消自己的
// if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) {
// throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF);
// }
//
// // 2. 通过删除流程实例,实现流程实例的取消,
// // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。
// deleteProcessInstance(cancelReqVO.getId(),
// BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason()));
//
// // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法
// }
//
// @Override
// public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) {
// // 1.1 校验流程实例存在
// ProcessInstance instance = getProcessInstance(cancelReqVO.getId());
// if (instance == null) {
// throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS);
// }
// // 1.2 管理员取消,不用校验是否为自己的
// AdminUserRespDTO user = adminUserApi.getUser(userId);
//
// // 2. 通过删除流程实例,实现流程实例的取消,
// // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。
// deleteProcessInstance(cancelReqVO.getId(),
// BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason()));
//
// // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法
// }
//
// @Override
// public void updateProcessInstanceWhenCancel(FlowableCancelledEvent event) {
// // 1. 判断是否为 Reject 不通过。如果是,则不进行更新.
// // 因为updateProcessInstanceReject 方法(审批不通过),已经进行更新了
// if (BpmDeleteReasonEnum.isRejectReason((String) event.getCause())) {
// return;
// }
//
// // 2. 更新流程实例 status
// runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS,
// BpmProcessInstanceStatusEnum.CANCEL.getStatus());
//
// // 3. 发送流程实例的状态事件
// // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance
// HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId());
// // 发送流程实例的状态事件
// processInstanceEventPublisher.sendProcessInstanceResultEvent(
// BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.CANCEL.getStatus()));
// }
//
// @Override
// public void updateProcessInstanceWhenApprove(ProcessInstance instance) {
// // 1. 更新流程实例 status
// runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS,
// BpmProcessInstanceStatusEnum.APPROVE.getStatus());
//
// // 2. 发送流程被【通过】的消息
// messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance));
//
// // 3. 发送流程实例的状态事件
// // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance
// HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId());
// processInstanceEventPublisher.sendProcessInstanceResultEvent(
// BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.APPROVE.getStatus()));
// }
//
// @Override
// @Transactional(rollbackFor = Exception.class)
// public void updateProcessInstanceReject(String id, String reason) {
// // 1. 更新流程实例 status
// runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus());
//
// // 2. 删除流程实例,以实现驳回任务时,取消整个审批流程
// ProcessInstance processInstance = getProcessInstance(id);
// deleteProcessInstance(id, StrUtil.format(BpmDeleteReasonEnum.REJECT_TASK.format(reason)));
//
// // 3. 发送流程被【不通过】的消息
// messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(processInstance, reason));
//
// // 4. 发送流程实例的状态事件
// processInstanceEventPublisher.sendProcessInstanceResultEvent(
// BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.REJECT.getStatus()));
// }
//
// private void deleteProcessInstance(String id, String reason) {
// runtimeService.deleteProcessInstance(id, reason);
// }
}

View File

@@ -0,0 +1,113 @@
package com.njcn.bpm.strategy;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.google.common.annotations.VisibleForTesting;
import com.njcn.bpm.enums.BpmTaskCandidateStrategyEnum;
import com.njcn.bpm.utils.BpmnModelUtils;
import com.njcn.bpm.utils.CollectionUtils;
import com.njcn.common.pojo.enums.common.DataStateEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.user.api.UserFeignClient;
import com.njcn.user.pojo.po.User;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import java.util.*;
/**
* @author 芋道源码
*/
@Slf4j
public class BpmTaskCandidateInvoker {
private final Map<BpmTaskCandidateStrategyEnum, IBpmTaskCandidateStrategy> strategyMap = new HashMap<>();
private final UserFeignClient adminUserApi;
public BpmTaskCandidateInvoker(List<IBpmTaskCandidateStrategy> strategyList,
UserFeignClient adminUserApi) {
strategyList.forEach(strategy -> {
IBpmTaskCandidateStrategy oldStrategy = strategyMap.put(strategy.getStrategy(), strategy);
Assert.isNull(oldStrategy, "策略(%s) 重复", strategy.getStrategy());
});
this.adminUserApi = adminUserApi;
}
/**
* 校验流程模型的任务分配规则全部都配置了
* 目的:如果有规则未配置,会导致流程任务找不到负责人,进而流程无法进行下去!
*
* @param bpmnBytes BPMN XML
*/
public void validateBpmnConfig(byte[] bpmnBytes) {
BpmnModel bpmnModel = BpmnModelUtils.getBpmnModel(bpmnBytes);
assert bpmnModel != null;
List<UserTask> userTaskList = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class);
// 遍历所有的 UserTask校验审批人配置
userTaskList.forEach(userTask -> {
// 1. 非空校验
Integer strategy = BpmnModelUtils.parseCandidateStrategy(userTask);
String param = BpmnModelUtils.parseCandidateParam(userTask);
if (strategy == null) {
throw new BusinessException("部署流程失败原因BPMN 流程图中,用户任务(" + userTask.getName() + ")的名字不存在");
}
IBpmTaskCandidateStrategy candidateStrategy = getCandidateStrategy(strategy);
if (candidateStrategy.isParamRequired() && StrUtil.isBlank(param)) {
throw new BusinessException("部署流程失败,原因:用户任务(" + userTask.getName() + ")未配置审批人,请点击【流程设计】按钮,选择该它的【任务(审批人)】进行配置");
}
// 2. 具体策略校验
getCandidateStrategy(strategy).validateParam(param);
});
}
/**
* 计算任务的候选人
*
* @param execution 执行任务
* @return 用户编号集合
*/
public List<String> calculateUsers(DelegateExecution execution) {
Integer strategy = BpmnModelUtils.parseCandidateStrategy(execution.getCurrentFlowElement());
String param = BpmnModelUtils.parseCandidateParam(execution.getCurrentFlowElement());
// 1.1 计算任务的候选人
List<String> userIds = getCandidateStrategy(strategy).calculateUsers(execution, param);
// 1.2 移除被禁用的用户
removeDisableUsers(userIds);
// 2. 校验是否有候选人
if (CollUtil.isEmpty(userIds)) {
log.error("[calculateUsers][流程任务({}/{}/{}) 任务规则({}/{}) 找不到候选人]", execution.getId(),
execution.getProcessDefinitionId(), execution.getCurrentActivityId(), strategy, param);
throw new BusinessException("操作失败,原因:找不到任务的审批人!");
}
return userIds;
}
@VisibleForTesting
void removeDisableUsers(List<String> assigneeUserIds) {
if (CollUtil.isEmpty(assigneeUserIds)) {
return;
}
List<User> users = adminUserApi.getUserByIdList(assigneeUserIds).getData();
Map<String, User> userMap = CollectionUtils.convertMap(users, User::getId);
assigneeUserIds.removeIf(id -> {
User user = userMap.get(id);
return user == null || !DataStateEnum.ENABLE.getCode().equals(user.getState());
});
}
private IBpmTaskCandidateStrategy getCandidateStrategy(Integer strategy) {
BpmTaskCandidateStrategyEnum strategyEnum = BpmTaskCandidateStrategyEnum.valueOf(strategy);
Assert.notNull(strategyEnum, "策略(%s) 不存在", strategy);
IBpmTaskCandidateStrategy strategyObj = strategyMap.get(strategyEnum);
Assert.notNull(strategyObj, "策略(%s) 不存在", strategy);
return strategyObj;
}
}

View File

@@ -0,0 +1,44 @@
package com.njcn.bpm.strategy;
import com.njcn.user.api.UserFeignClient;
import com.njcn.bpm.utils.StrUtils;
import org.flowable.engine.delegate.DelegateExecution;
import com.njcn.bpm.enums.BpmTaskCandidateStrategyEnum;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
import java.util.Set;
/**
* 角色 {@link IBpmTaskCandidateStrategy} 实现类
*
* @author kyle
*/
@Component
public class BpmTaskCandidateRoleStrategy implements IBpmTaskCandidateStrategy {
@Resource
private UserFeignClient userFeignClient;
// @Resource
// private PermissionApi permissionApi;
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
return BpmTaskCandidateStrategyEnum.ROLE;
}
@Override
public void validateParam(String param) {
// Set<Long> roleIds = StrUtils.splitToLongSet(param);
// roleApi.validRoleList(roleIds);
}
@Override
public List<String> calculateUsers(DelegateExecution execution, String param) {
List<String> roleIds = StrUtils.splitToStringList(param);
return userFeignClient.getUserIdByRoleId(roleIds).getData();
}
}

View File

@@ -0,0 +1,74 @@
package com.njcn.bpm.strategy;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import com.njcn.bpm.enums.BpmTaskCandidateStrategyEnum;
import com.njcn.bpm.service.task.IBpmProcessInstanceService;
import com.njcn.bpm.utils.BpmnModelUtils;
import com.njcn.bpm.utils.FlowableUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.*;
/**
*
* @author 芋道源码
*/
@Component
public class BpmTaskCandidateStartUserSelectStrategy implements IBpmTaskCandidateStrategy {
@Resource
@Lazy // 延迟加载,避免循环依赖
private IBpmProcessInstanceService processInstanceService;
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
return BpmTaskCandidateStrategyEnum.START_USER_SELECT;
}
@Override
public void validateParam(String param) {}
@Override
public List<String> calculateUsers(DelegateExecution execution, String param) {
ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
Assert.notNull(processInstance, "流程实例({})不能为空", execution.getProcessInstanceId());
Map<String, List<String>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance);
Assert.notNull(startUserSelectAssignees, "流程实例({}) 的发起人自选审批人不能为空",
execution.getProcessInstanceId());
// 获得审批人
List<String> assignees = startUserSelectAssignees.get(execution.getCurrentActivityId());
return new ArrayList<>(assignees);
}
@Override
public boolean isParamRequired() {
return false;
}
/**
* 获得发起人自选审批人的 UserTask 列表
*
* @param bpmnModel BPMN 模型
* @return UserTask 列表
*/
public static List<UserTask> getStartUserSelectUserTaskList(BpmnModel bpmnModel) {
if (bpmnModel == null) {
return null;
}
List<UserTask> userTaskList = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class);
if (CollUtil.isEmpty(userTaskList)) {
return null;
}
userTaskList.removeIf(userTask -> !Objects.equals(BpmnModelUtils.parseCandidateStrategy(userTask),
BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy()));
return userTaskList;
}
}

View File

@@ -0,0 +1,39 @@
package com.njcn.bpm.strategy;
import com.njcn.bpm.enums.BpmTaskCandidateStrategyEnum;
import com.njcn.bpm.utils.StrUtils;
import com.njcn.user.api.UserFeignClient;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
/**
* 用户 {@link IBpmTaskCandidateStrategy} 实现类
*
* @author kyle
*/
@Component
public class BpmTaskCandidateUserStrategy implements IBpmTaskCandidateStrategy {
@Resource
private UserFeignClient adminUserApi;
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
return BpmTaskCandidateStrategyEnum.USER;
}
@Override
public void validateParam(String param) {
// adminUserApi.validateUserList(StrUtils.splitToLongSet(param));
//暂时默认均有效后期可以加强管理todo...
}
@Override
public List<String> calculateUsers(DelegateExecution execution, String param) {
return StrUtils.splitToStringList(param);
}
}

View File

@@ -0,0 +1,49 @@
package com.njcn.bpm.strategy;
import com.njcn.bpm.enums.BpmTaskCandidateStrategyEnum;
import org.flowable.engine.delegate.DelegateExecution;
import java.util.List;
import java.util.Set;
/**
* BPM 任务的候选人的策略接口
*
* 例如说:分配审批人
*
* @author 芋道源码
*/
public interface IBpmTaskCandidateStrategy {
/**
* 对应策略
*
* @return 策略
*/
BpmTaskCandidateStrategyEnum getStrategy();
/**
* 校验参数
*
* @param param 参数
*/
void validateParam(String param);
/**
* 基于执行任务,获得任务的候选用户们
*
* @param execution 执行任务
* @return 用户编号集合
*/
List<String> calculateUsers(DelegateExecution execution, String param);
/**
* 是否一定要输入参数
*
* @return 是否
*/
default boolean isParamRequired() {
return true;
}
}

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

View File

@@ -0,0 +1,69 @@
#当前服务的基本信息
microservice:
ename: @artifactId@
name: '@name@'
version: @version@
sentinel:
url: @sentinel.url@
gateway:
url: @gateway.url@
server:
port: 10321
#feign接口开启服务熔断降级处理
feign:
sentinel:
enabled: true
spring:
application:
name: @artifactId@
#nacos注册中心以及配置中心的指定
cloud:
nacos:
discovery:
ip: @service.server.url@
server-addr: @nacos.url@
namespace: @nacos.namespace@
config:
server-addr: @nacos.url@
namespace: @nacos.namespace@
file-extension: yaml
shared-configs:
- data-id: share-config.yaml
refresh: true
- data-Id: bpm-config.yaml
refresh: true
main:
allow-bean-definition-overriding: true
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB
flowable:
database-schema-update: false
db-history-used: true # flowable6 默认 true 生成信息表,无需手动设置
check-process-definitions: false # 设置为 false禁用 /resources/processes 自动部署 BPMN XML 流程
history-level: full # full保存历史数据的最高级别可保存全部流程相关细节包括流程流转各节点参数
async-executor-activate: false
#项目日志的配置
logging:
config: http://@nacos.url@/nacos/v1/cs/configs?tenant=@nacos.namespace@&group=DEFAULT_GROUP&dataId=logback.xml
level:
root: info
#mybatis配置信息
mybatis-plus:
type-aliases-package: com.njcn.bpm.pojo
gw:
url: dwzyywzt-pms3-proxy.com
code: 13B9B47F1E483324E05338297A0A0595
mqtt:
client-id: @artifactId@${random.value}