Merge branch 'feature/REQ-1609-syl' into 'feature/REQ-1609'

feat: 新增审批流选人逻辑

See merge request universal/infrastructure/backend/workflow-engine!1
This commit is contained in:
王粒 2023-11-22 09:04:51 +00:00
commit 845ff96bd8
22 changed files with 808 additions and 60 deletions

View File

@ -65,6 +65,11 @@
<artifactId>maokai-api</artifactId>
<version>${axzo-dependencies.version}</version>
</dependency>
<dependency>
<groupId>cn.axzo.karma</groupId>
<artifactId>karma-api</artifactId>
<version>${axzo-dependencies.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>

View File

@ -1,19 +1,25 @@
package cn.axzo.workflow.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 审批人所在范围枚举
*
* @author wangli
* @since 2023/11/16 10:14
*/
@Getter
@AllArgsConstructor
public enum ApproverScopeEnum {
entWorkspace("entWorkspace", "企业工作台"),
projectWorkspace("projectWorkspace", "项目工作台"),
preTaskUser("preTaskUser", "上节点审批人所在单位"),
preTaskSpecified("preTaskSpecified", "上节点审批人指定"),
entWorkspace("entWorkspace", "企业工作台", "entWorkspaceSelector"),
projectWorkspace("projectWorkspace", "项目工作台","projectWorkspaceSelector"),
preTaskUser("preTaskUser", "上节点审批人所在单位","preTaskUserSelector"),
preTaskSpecified("preTaskSpecified", "上节点审批人指定","preTaskUserSelector"),
;
private String type;
private String desc;
private String processor;
ApproverScopeEnum(String type, String desc) {
this.type = type;
@ -35,4 +41,8 @@ public enum ApproverScopeEnum {
public void setDesc(String desc) {
this.desc = desc;
}
public boolean selectWorkspace() {
return this == ApproverScopeEnum.projectWorkspace;
}
}

View File

@ -0,0 +1,31 @@
package cn.axzo.workflow.common.enums;
import java.util.Arrays;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author syl
* @date 2023/11/21
*/
@Getter
@AllArgsConstructor
public enum WorkspaceType {
/**
* 工作台类型
* 1- 企业 2-项目 6-oms
*/
ENT(1, "企业"),
WORKSPACE(2, "项目"),
OMS(6, "oms工作台");
private Integer code;
private String desc;
public static WorkspaceType getType(Integer code) {
return Arrays.stream(values()).filter(it -> it.getCode().equals(code))
.findFirst()
.orElse(null);
}
}

View File

@ -1,12 +1,14 @@
package cn.axzo.workflow.common.model.request.bpmn.task;
import java.io.Serializable;
import javax.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.springframework.util.StringUtils;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
/**
* 统一的工作流中的人员模型,兼容不同的业务方,如安心筑"唯一"人的定义需要工作台+身份 ID+身份类型;<br/>
@ -17,6 +19,9 @@ import java.io.Serializable;
* @author wangli
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class BpmnTaskDelegateAssigner implements Serializable {

View File

@ -88,5 +88,14 @@
<groupId>cn.axzo.maokai</groupId>
<artifactId>maokai-api</artifactId>
</dependency>
<!-- api -->
<dependency>
<groupId>cn.axzo.tyr</groupId>
<artifactId>tyr-api</artifactId>
</dependency>
<dependency>
<groupId>cn.axzo.karma</groupId>
<artifactId>karma-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,127 @@
package cn.axzo.workflow.core.common.utils;
import java.util.function.Consumer;
import java.util.function.Supplier;
import cn.axzo.basics.common.util.AssertUtil;
import cn.axzo.framework.domain.ServiceException;
import cn.axzo.framework.domain.web.BizException;
import cn.axzo.framework.domain.web.result.ApiPageResult;
import cn.axzo.framework.domain.web.result.ApiResult;
import cn.azxo.framework.common.model.CommonResponse;
import cn.hutool.core.lang.Assert;
import cn.hutool.http.HttpStatus;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import static cn.axzo.framework.domain.web.code.BaseCode.BAD_REQUEST;
/**
* 内部api 使用 服务与 yoke 下游服务 下游服务统一使用 ApiResult
* @author tanjie@axzo.cn
* @date 2022/5/23 11:08
*/
@Slf4j
public class RpcInternalUtil {
/**
* 常用的RPC请求返回值解析如果 被请求方 返回非200会抛出异常
*/
public static <T> ApiResult<T> rpcProcessor(Supplier<ApiResult<T>> supplier, String operationType, Object... param) {
return rpcProcessorMayThrow(supplier, operationType, (msg) -> {
throw new ServiceException(msg);
}, param);
}
public static <T> ApiResult<T> rpcProcessorMayThrow(Supplier<ApiResult<T>> supplier, String operationType, Consumer<String> throwConsumer, Object... param) {
AssertUtil.notNull(throwConsumer, "自定义的异常处理不可为空");
log.info(operationType + "-Param: " + JSONUtil.toJsonStr(param));
ApiResult<T> result = null;
try {
result = supplier.get();
} catch (Throwable e) {
log.warn("rpc process error:{}", e.getMessage());
throwConsumer.accept("服务调用异常");
}
log.info(operationType + "-Result: " + JSONUtil.toJsonStr(result));
Assert.notNull(result, "服务调用异常");
// 200自定义处理
if (HttpStatus.HTTP_OK != result.getCode()) {
throwConsumer.accept(result.getMsg());
}
return result;
}
/**
* 常用的RPC请求返回值解析如果 被请求方 返回非200会抛出异常
*/
public static <T> ApiPageResult<T> rpcPageProcessor(Supplier<ApiPageResult<T>> supplier, String operationType, Object... param) {
return rpcPageProcessorMayThrow(supplier, operationType, (msg) -> {
throw new ServiceException(msg);
}, param);
}
public static <T> ApiPageResult<T> rpcPageProcessorMayThrow(Supplier<ApiPageResult<T>> supplier, String operationType, Consumer<String> throwConsumer, Object... param) {
AssertUtil.notNull(throwConsumer, "自定义的异常处理不可为空");
log.info(operationType + "-Param: " + JSONUtil.toJsonStr(param));
ApiPageResult<T> result = null;
try {
result = supplier.get();
} catch (Throwable t) {
log.warn("rpc process error:{}", t.getMessage());
throwConsumer.accept("服务调用异常");
}
log.info(operationType + "-Result: " + JSONUtil.toJsonStr(result));
Assert.notNull(result, "服务调用异常");
// 200自定义处理
if (HttpStatus.HTTP_OK != result.getCode()) {
throwConsumer.accept(result.getMsg());
}
return result;
}
public static <T> T checkAndGetData(ApiResult<T> result) {
if (result.isError()) {
throw new BizException(result.getRespCode(), result.getMsg());
}
T data = result.getData();
if (data == null) {
throw new BizException(BAD_REQUEST, "数据不存在");
}
return data;
}
public static <T> CommonResponse<T> commonRpcProcessor(Supplier<CommonResponse<T>> supplier, String operationType, Object... param) {
return commonRpcProcessorMayThrow(supplier, operationType, (msg) -> {
throw new ServiceException(msg);
}, param);
}
public static <T> CommonResponse<T> commonRpcProcessorMayThrow(Supplier<CommonResponse<T>> supplier, String operationType,
Consumer<String> throwConsumer, Object... param) {
AssertUtil.notNull(throwConsumer, "自定义的异常处理不可为空");
log.info(operationType + "-Param: " + JSONUtil.toJsonStr(param));
CommonResponse<T> result = null;
try {
result = supplier.get();
} catch (Throwable e) {
log.warn("rpc process error:{}", e.getMessage());
throwConsumer.accept("服务调用异常");
}
log.info(operationType + "-Result: " + JSONUtil.toJsonStr(result));
Assert.notNull(result, "服务调用异常");
// 200自定义处理
if (HttpStatus.HTTP_OK != result.getCode()) {
throwConsumer.accept(result.getMsg());
}
return result;
}
}

View File

@ -12,14 +12,18 @@ import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.http.HttpStatus;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.UserTask;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import com.alibaba.fastjson.JSON;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.util.CollectionUtils;
/**
* 抽象的流程任务审批人选择器
@ -39,7 +43,7 @@ public abstract class AbstractBpmnTaskAssigneeSelector implements BpmnTaskAssign
"未获取到”审批人所在范围”的配置信息,请检查流程配置"));
}
@Override
public List<BpmnTaskDelegateAssigner> select(UserTask userTask) {
public List<BpmnTaskDelegateAssigner> select(UserTask userTask, DelegateExecution execution) {
FlowTaskAssignerReq query = buildQuery(userTask);
List<FlowTaskAssigner> flowTaskAssigners = null;
try {
@ -67,4 +71,14 @@ public abstract class AbstractBpmnTaskAssigneeSelector implements BpmnTaskAssign
return result.getData();
}
@Override
public List<String> getTypes(UserTask userTask) {
// 默认解析格式[{"name":"预算员", "value":"xxxx"}]
return BpmnMetaParserHelper.getApproverSpecifyValue(userTask)
.map(value -> value.stream()
.map(s -> Optional.ofNullable(JSON.parseObject(s).getString("value"))
.orElse(null))
.filter(Objects::nonNull)
.collect(Collectors.toList())).orElse(Collections.emptyList());
}
}

View File

@ -1,8 +1,28 @@
package cn.axzo.workflow.core.deletage;
import cn.axzo.karma.client.feign.FlowSupportApi;
import cn.axzo.karma.client.model.request.ListFlowTaskAssignerReq;
import cn.axzo.karma.client.model.response.FlowTaskAssignerResp;
import cn.axzo.maokai.api.vo.request.FlowTaskAssignerReq;
import cn.axzo.workflow.common.enums.ApproverEmptyHandleTypeEnum;
import cn.axzo.workflow.common.enums.ApproverScopeEnum;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.core.common.exception.WorkflowEngineException;
import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper;
import cn.axzo.workflow.core.common.utils.RpcInternalUtil;
import cn.axzo.workflow.core.deletage.approverscope.ApproverScopeDto;
import cn.axzo.workflow.core.deletage.approverscope.ApproverScopeSelector;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
@ -11,9 +31,14 @@ import org.springframework.stereotype.Component;
* @author wangli
* @since 2023/11/16 11:44
*/
@Slf4j
@Component
public class AdminTaskAssigneeSelector extends AbstractBpmnTaskAssigneeSelector {
@Autowired
private FlowSupportApi flowSupportApi;
@Autowired
private ApplicationContext applicationContext;
@Override
public boolean support(String param) {
@ -24,4 +49,46 @@ public class AdminTaskAssigneeSelector extends AbstractBpmnTaskAssigneeSelector
protected FlowTaskAssignerReq buildQuery(UserTask userTask) {
return null;
}
@Override
public List<BpmnTaskDelegateAssigner> select(UserTask userTask, DelegateExecution execution) {
Optional<ApproverScopeEnum> approverScopeOpt = BpmnMetaParserHelper
.getApproverScope(userTask);
if (!approverScopeOpt.isPresent()) {
return Collections.emptyList();
}
ApproverScopeSelector scopeProcessor = applicationContext
.getBean(approverScopeOpt.get().getProcessor(),
ApproverScopeSelector.class);
ApproverScopeDto scopeDto = scopeProcessor.select(userTask, execution);
if (CollUtil.isEmpty(scopeDto.getOrgScopes())) {
log.info("adminTaskAssignee no get orgScopes");
return Collections.emptyList();
}
List<FlowTaskAssignerResp> flowTaskAssigners = null;
try {
ListFlowTaskAssignerReq req = ListFlowTaskAssignerReq.builder()
.orgScopes(scopeDto.getOrgScopes().stream()
.map(e -> BeanUtil
.copyProperties(e, ListFlowTaskAssignerReq.OrgScope.class))
.collect(Collectors.toList()))
.workerTeamScopes(scopeDto.getWorkerTeamScopes().stream()
.map(w -> BeanUtil
.copyProperties(w, ListFlowTaskAssignerReq.OrgScope.class))
.collect(Collectors.toList()))
.build();
// todo 调用中台提供查询管理员的api
flowTaskAssigners = RpcInternalUtil.rpcProcessor(() ->
flowSupportApi.listTaskAssignerByIdentity(req), "通过管理员查询审批人").getData();
} catch (Exception e) {
throw new WorkflowEngineException("调用 API 查询审批候选人出现异常: " + e.getMessage());
}
if (CollUtil.isEmpty(flowTaskAssigners)) {
return Collections.emptyList();
}
return BeanUtil.copyToList(flowTaskAssigners, BpmnTaskDelegateAssigner.class);
}
}

View File

@ -1,9 +1,10 @@
package cn.axzo.workflow.core.deletage;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import org.flowable.bpmn.model.UserTask;
import java.util.Collections;
import java.util.List;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
/**
* 任务节点的审批人查询器接口
@ -15,18 +16,18 @@ public interface BpmnTaskAssigneeSelector {
/**
* 判断是否支持当前审批指定类型
*
* @param param
* @return
*/
boolean support(String param);
/**
* 查询具体审批人
*
* @param userTask
* @return
*/
List<BpmnTaskDelegateAssigner> select(UserTask userTask);
List<BpmnTaskDelegateAssigner> select(UserTask userTask, DelegateExecution execution);
/**
* 审批人筛选项获取类型
*/
default List<String> getTypes(UserTask userTask) {
return Collections.emptyList();
}
}

View File

@ -5,11 +5,11 @@ import cn.axzo.workflow.common.enums.ApproverSpecifyEnum;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper;
import com.alibaba.fastjson.JSON;
import org.flowable.bpmn.model.UserTask;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
/**
* 基于"固定人元"查询审批人
@ -25,7 +25,7 @@ public class FixedPersonTaskAssigneeSelector extends AbstractBpmnTaskAssigneeSel
}
@Override
public List<BpmnTaskDelegateAssigner> select(UserTask userTask) {
public List<BpmnTaskDelegateAssigner> select(UserTask userTask, DelegateExecution execution) {
List<BpmnTaskDelegateAssigner> assigners = new ArrayList<>();
BpmnMetaParserHelper.getApproverSpecifyValue(userTask)
.ifPresent(list -> list.forEach(i -> assigners.add(JSON.parseObject(i,

View File

@ -1,8 +1,28 @@
package cn.axzo.workflow.core.deletage;
import cn.axzo.karma.client.feign.FlowSupportApi;
import cn.axzo.karma.client.model.request.ListFlowTaskAssignerReq;
import cn.axzo.karma.client.model.response.FlowTaskAssignerResp;
import cn.axzo.maokai.api.vo.request.FlowTaskAssignerReq;
import cn.axzo.workflow.common.enums.ApproverScopeEnum;
import cn.axzo.workflow.common.enums.ApproverSpecifyEnum;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.core.common.exception.WorkflowEngineException;
import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper;
import cn.axzo.workflow.core.common.utils.RpcInternalUtil;
import cn.axzo.workflow.core.deletage.approverscope.ApproverScopeDto;
import cn.axzo.workflow.core.deletage.approverscope.ApproverScopeSelector;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
@ -11,8 +31,15 @@ import org.springframework.stereotype.Component;
* @author wangli
* @since 2023/11/16 11:38
*/
@Slf4j
@Component
public class IdentityTaskAssigneeSelector extends AbstractBpmnTaskAssigneeSelector {
@Autowired
private FlowSupportApi flowSupportApi;
@Autowired
private ApplicationContext applicationContext;
@Override
public boolean support(String param) {
return ApproverSpecifyEnum.identity.getType().equals(param);
@ -23,4 +50,44 @@ public class IdentityTaskAssigneeSelector extends AbstractBpmnTaskAssigneeSelect
return null;
}
@Override
public List<BpmnTaskDelegateAssigner> select(UserTask userTask, DelegateExecution execution) {
Optional<ApproverScopeEnum> approverScopeOpt = BpmnMetaParserHelper.getApproverScope(userTask);
List<String> identityCodes = super.getTypes(userTask);
if (CollUtil.isEmpty(identityCodes) || !approverScopeOpt.isPresent()) {
return Collections.emptyList();
}
ApproverScopeSelector scopeProcessor = applicationContext
.getBean(approverScopeOpt.get().getProcessor(),
ApproverScopeSelector.class);
ApproverScopeDto scopeDto = scopeProcessor.select(userTask, execution);
if (CollUtil.isEmpty(scopeDto.getOrgScopes())) {
log.info("identityTaskAssignee no get orgScopes");
return Collections.emptyList();
}
List<FlowTaskAssignerResp> flowTaskAssigners = null;
try {
ListFlowTaskAssignerReq req = ListFlowTaskAssignerReq.builder()
.orgScopes(scopeDto.getOrgScopes().stream()
.map(e -> BeanUtil
.copyProperties(e, ListFlowTaskAssignerReq.OrgScope.class))
.collect(Collectors.toList()))
.workerTeamScopes(scopeDto.getWorkerTeamScopes().stream()
.map(w -> BeanUtil
.copyProperties(w, ListFlowTaskAssignerReq.OrgScope.class))
.collect(Collectors.toList()))
.identityCodes(identityCodes)
.build();
flowTaskAssigners = RpcInternalUtil.rpcProcessor(() ->
flowSupportApi.listTaskAssignerByIdentity(req), "通过身份查询审批人").getData();
} catch (Exception e) {
throw new WorkflowEngineException("调用 API 查询审批候选人出现异常: " + e.getMessage());
}
if (CollUtil.isEmpty(flowTaskAssigners)) {
return Collections.emptyList();
}
return BeanUtil.copyToList(flowTaskAssigners, BpmnTaskDelegateAssigner.class);
}
}

View File

@ -6,6 +6,7 @@ import org.flowable.bpmn.model.UserTask;
import org.springframework.stereotype.Component;
/**
* todo 本期需求不实现
* 给予"发起人多级主管"查询审批人
*
* @author wangli

View File

@ -1,18 +1,51 @@
package cn.axzo.workflow.core.deletage;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR;
import cn.axzo.maokai.api.client.OrganizationalNodeUserApi;
import cn.axzo.maokai.api.vo.request.FlowTaskAssignerReq;
import cn.axzo.maokai.api.vo.request.FlowTaskAssignerReq.IdentityPair;
import cn.axzo.maokai.api.vo.response.FlowTaskAssigner;
import cn.axzo.workflow.common.enums.ApproverScopeEnum;
import cn.axzo.workflow.common.enums.ApproverSpecifyEnum;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.core.common.exception.WorkflowEngineException;
import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper;
import cn.axzo.workflow.core.common.utils.RpcInternalUtil;
import cn.axzo.workflow.core.deletage.approverscope.ApproverScopeDto;
import cn.axzo.workflow.core.deletage.approverscope.ApproverScopeSelector;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
* 基于"发起人多级主管"查询审批人
* 基于"发起人主管"查询审批人
*
* @author wangli
* @since 2023/11/16 11:41
*/
@Slf4j
@Component
public class InitiatorLeaderTaskAssigneeSelector extends AbstractBpmnTaskAssigneeSelector {
@Autowired
private OrganizationalNodeUserApi organizationalNodeUserApi;
@Autowired
private ApplicationContext applicationContext;
@Override
public boolean support(String param) {
return ApproverSpecifyEnum.initiatorLeader.getType().equals(param);
@ -22,4 +55,53 @@ public class InitiatorLeaderTaskAssigneeSelector extends AbstractBpmnTaskAssigne
protected FlowTaskAssignerReq buildQuery(UserTask userTask) {
return null;
}
@Override
public List<BpmnTaskDelegateAssigner> select(UserTask userTask, DelegateExecution execution) {
Optional<ApproverScopeEnum> approverScopeOpt = BpmnMetaParserHelper
.getApproverScope(userTask);
List<String> positions = super.getTypes(userTask);
Map<String, Object> variables = execution.getVariables();
if (CollUtil.isEmpty(positions) || !approverScopeOpt.isPresent() || CollUtil
.isEmpty(variables)) {
return Collections.emptyList();
}
ApproverScopeSelector scopeProcessor = applicationContext
.getBean(approverScopeOpt.get().getProcessor(), ApproverScopeSelector.class);
ApproverScopeDto scopeDto = scopeProcessor.select(userTask, execution);
// 获取发起人
BpmnTaskDelegateAssigner initiator = (BpmnTaskDelegateAssigner) variables
.get(INTERNAL_INITIATOR);
if (Objects.isNull(initiator) || StringUtils.isBlank(initiator.getAssigneeType())
|| CollUtil.isEmpty(scopeDto.getOrgScopes())) {
log.info("initiatorLeaderTaskAssignee can not query assigner info or orgScopes");
return Collections.emptyList();
}
List<FlowTaskAssigner> flowTaskAssigners = null;
try {
FlowTaskAssignerReq req = FlowTaskAssignerReq.builder()
.orgScopes(scopeDto.getOrgScopes().stream()
.map(o -> FlowTaskAssignerReq.OrgScope.builder()
.ouId(o.getOuId())
.workspaceId(o.getWorkspaceId())
.build())
.collect(Collectors.toList()))
.sponsor(IdentityPair.builder()
.identityId(Long.valueOf(initiator.getAssignee()))
.identityType(Integer.valueOf(initiator.getAssigneeType())).build())
.build();
flowTaskAssigners = RpcInternalUtil.rpcProcessor(() ->
organizationalNodeUserApi.listFlowTaskAssigner(req), "通过发起人主管查询审批人").getData();
} catch (Exception e) {
throw new WorkflowEngineException("调用 API 查询审批候选人出现异常: " + e.getMessage());
}
if (CollUtil.isEmpty(flowTaskAssigners)) {
return Collections.emptyList();
}
return BeanUtil.copyToList(flowTaskAssigners, BpmnTaskDelegateAssigner.class);
}
}

View File

@ -2,6 +2,7 @@ package cn.axzo.workflow.core.deletage;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import java.util.ArrayList;
import java.util.List;
@ -20,7 +21,7 @@ public class MockTaskAssigneeSelector implements BpmnTaskAssigneeSelector {
}
@Override
public List<BpmnTaskDelegateAssigner> select(UserTask userTask) {
public List<BpmnTaskDelegateAssigner> select(UserTask userTask, DelegateExecution execution) {
List<BpmnTaskDelegateAssigner> users = new ArrayList<>();
return users;
}

View File

@ -1,8 +1,27 @@
package cn.axzo.workflow.core.deletage;
import cn.axzo.maokai.api.client.OrganizationalNodeUserApi;
import cn.axzo.maokai.api.vo.request.FlowTaskAssignerReq;
import cn.axzo.maokai.api.vo.response.FlowTaskAssigner;
import cn.axzo.workflow.common.enums.ApproverScopeEnum;
import cn.axzo.workflow.common.enums.ApproverSpecifyEnum;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.core.common.exception.WorkflowEngineException;
import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper;
import cn.axzo.workflow.core.common.utils.RpcInternalUtil;
import cn.axzo.workflow.core.deletage.approverscope.ApproverScopeDto;
import cn.axzo.workflow.core.deletage.approverscope.ApproverScopeSelector;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
@ -11,8 +30,16 @@ import org.springframework.stereotype.Component;
* @author wangli
* @since 2023/11/16 11:35
*/
@Slf4j
@Component
public class PositionTaskAssigneeSelector extends AbstractBpmnTaskAssigneeSelector {
@Autowired
private OrganizationalNodeUserApi organizationalNodeUserApi;
@Autowired
private ApplicationContext applicationContext;
@Override
public boolean support(String param) {
return ApproverSpecifyEnum.position.getType().equals(param);
@ -22,4 +49,40 @@ public class PositionTaskAssigneeSelector extends AbstractBpmnTaskAssigneeSelect
protected FlowTaskAssignerReq buildQuery(UserTask userTask) {
return null;
}
@Override
public List<BpmnTaskDelegateAssigner> select(UserTask userTask, DelegateExecution execution) {
Optional<ApproverScopeEnum> approverScopeOpt = BpmnMetaParserHelper.getApproverScope(userTask);
List<String> positions = super.getTypes(userTask);
if (CollUtil.isEmpty(positions) || !approverScopeOpt.isPresent()) {
return Collections.emptyList();
}
ApproverScopeSelector scopeProcessor = applicationContext.getBean(approverScopeOpt.get().getProcessor(),
ApproverScopeSelector.class);
ApproverScopeDto scopeDto = scopeProcessor.select(userTask, execution);
if (CollUtil.isEmpty(scopeDto.getOrgScopes())) {
log.info("positionTaskAssignee no get orgScopes");
return Collections.emptyList();
}
List<FlowTaskAssigner> flowTaskAssigners = null;
try {
FlowTaskAssignerReq req = FlowTaskAssignerReq.builder()
.orgScopes(scopeDto.getOrgScopes().stream()
.map(e -> BeanUtil.copyProperties(e, FlowTaskAssignerReq.OrgScope.class))
.collect(Collectors.toList()))
.jobCodes(positions)
.build();
flowTaskAssigners = RpcInternalUtil.rpcProcessor(() ->
organizationalNodeUserApi.listFlowTaskAssigner(req), "通过岗位查询审批人").getData();
} catch (Exception e) {
throw new WorkflowEngineException("调用 API 查询审批候选人出现异常: " + e.getMessage());
}
if (CollUtil.isEmpty(flowTaskAssigners)) {
return Collections.emptyList();
}
return BeanUtil.copyToList(flowTaskAssigners, BpmnTaskDelegateAssigner.class);
}
}

View File

@ -1,8 +1,29 @@
package cn.axzo.workflow.core.deletage;
import cn.axzo.maokai.api.vo.request.FlowTaskAssignerReq;
import cn.axzo.tyr.client.feign.TyrSaasRoleUserApi;
import cn.axzo.tyr.client.model.roleuser.dto.SaasRoleUserDTO;
import cn.axzo.tyr.client.model.roleuser.req.RoleUserParam;
import cn.axzo.workflow.common.enums.ApproverScopeEnum;
import cn.axzo.workflow.common.enums.ApproverSpecifyEnum;
import cn.axzo.workflow.common.model.dto.CooperationOrgDTO.OrgScope;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.core.common.exception.WorkflowEngineException;
import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper;
import cn.axzo.workflow.core.common.utils.RpcInternalUtil;
import cn.axzo.workflow.core.deletage.approverscope.ApproverScopeDto;
import cn.axzo.workflow.core.deletage.approverscope.ApproverScopeSelector;
import cn.hutool.core.collection.CollUtil;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
@ -11,8 +32,14 @@ import org.springframework.stereotype.Component;
* @author wangli
* @since 2023/11/16 11:37
*/
@Slf4j
@Component
public class RoleTaskAssigneeSelector extends AbstractBpmnTaskAssigneeSelector {
@Autowired
private ApplicationContext applicationContext;
@Autowired
private TyrSaasRoleUserApi tyrSaasRoleUserApi;
@Override
public boolean support(String param) {
return ApproverSpecifyEnum.role.getType().equals(param);
@ -22,4 +49,59 @@ public class RoleTaskAssigneeSelector extends AbstractBpmnTaskAssigneeSelector {
protected FlowTaskAssignerReq buildQuery(UserTask userTask) {
return null;
}
@Override
public List<BpmnTaskDelegateAssigner> select(UserTask userTask, DelegateExecution execution) {
Optional<ApproverScopeEnum> approverScopeOpt = BpmnMetaParserHelper.getApproverScope(userTask);
List<String> roles = super.getTypes(userTask);
if (CollUtil.isEmpty(roles) || !approverScopeOpt.isPresent()) {
return Collections.emptyList();
}
Set<Long> roleIds = roles.stream().map(Long::valueOf).collect(Collectors.toSet());
ApproverScopeSelector scopeProcessor = applicationContext.getBean(approverScopeOpt.get().getProcessor(),
ApproverScopeSelector.class);
ApproverScopeDto scopeDto = scopeProcessor.select(userTask, execution);
if (CollUtil.isEmpty(scopeDto.getOrgScopes())) {
log.info("roleTaskAssignee no get orgScopes");
return Collections.emptyList();
}
Set<Long> workspaceIdSet = scopeDto.getOrgScopes().stream()
.map(OrgScope::getWorkspaceId)
.collect(Collectors.toSet());
List<SaasRoleUserDTO> flowTaskAssigners = null;
try {
// ouIdsworkspaceIds 只能传其中一个 然后代码过滤
RoleUserParam param = RoleUserParam.builder()
.roleIds(roleIds)
.ouIds(scopeDto.getOrgScopes().stream()
.map(OrgScope::getOuId)
.collect(Collectors.toList()))
.build();
param.setPageSize(-1L);
flowTaskAssigners = RpcInternalUtil.rpcProcessor(() ->
tyrSaasRoleUserApi.roleUserList(param), "通过角色查询审批人").getData()
.stream()
.filter(f -> workspaceIdSet.contains(f.getWorkspaceId()))
.collect(Collectors.toList());
} catch (Exception e) {
throw new WorkflowEngineException("调用 API 查询审批候选人出现异常: " + e.getMessage());
}
if (CollUtil.isEmpty(flowTaskAssigners)) {
return Collections.emptyList();
}
return flowTaskAssigners.stream().map(u -> BpmnTaskDelegateAssigner.builder()
.assignee(String.valueOf(u.getIdentityId()))
.assigneeType(String.valueOf(u.getIdentityType()))
.tenantId(String.valueOf(u.getWorkspaceId()))
.ouId(String.valueOf(u.getOuId()))
.personId(String.valueOf(u.getNaturalPersonId()))
.build())
.collect(Collectors.toList());
}
}

View File

@ -0,0 +1,29 @@
package cn.axzo.workflow.core.deletage.approverscope;
import cn.axzo.workflow.common.model.dto.CooperationOrgDTO.OrgScope;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author syl
* @date 2023/11/21
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ApproverScopeDto {
/**
* 组织架构范围
* 可能是企业项目等
**/
private List<OrgScope> orgScopes;
/**
* 班组组织架构范围 - 目前用于选择身份
**/
private List<OrgScope> workerTeamScopes;
}

View File

@ -0,0 +1,15 @@
package cn.axzo.workflow.core.deletage.approverscope;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
/**
* @author syl
* @date 2023/11/21
*/
public interface ApproverScopeSelector {
default ApproverScopeDto select(UserTask userTask, DelegateExecution execution){
return ApproverScopeDto.builder().build();
}
}

View File

@ -0,0 +1,44 @@
package cn.axzo.workflow.core.deletage.approverscope;
import static cn.axzo.workflow.common.constant.BpmnConstants.BIZ_ORG_RELATION;
import cn.axzo.workflow.common.enums.WorkspaceType;
import cn.axzo.workflow.common.model.dto.CooperationOrgDTO;
import cn.axzo.workflow.common.model.dto.CooperationOrgDTO.OrgScope;
import cn.hutool.core.collection.CollUtil;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
/**
* @author syl
* @date 2023/11/21
*/
@Component
public class EntWorkspaceSelector implements ApproverScopeSelector {
@Override
public ApproverScopeDto select(UserTask userTask, DelegateExecution execution) {
// 获取组织信息
CooperationOrgDTO orgDTO =(CooperationOrgDTO) execution.getVariables().get(BIZ_ORG_RELATION);
if (Objects.isNull(orgDTO)) {
return ApproverScopeDto.builder().build();
}
List<OrgScope> orgScopes = orgDTO.getOrgScopes().stream()
.filter(o -> WorkspaceType.ENT == WorkspaceType.getType(o.getWorkspaceType()))
.collect(Collectors.toList());
List<OrgScope> workerTeamScopes = CollUtil.defaultIfEmpty(orgDTO.getWorkerTeamScopes(),
Collections.emptyList())
.stream()
.filter(o -> WorkspaceType.ENT == WorkspaceType.getType(o.getWorkspaceType()))
.collect(Collectors.toList());
return ApproverScopeDto.builder().orgScopes(orgScopes)
.workerTeamScopes(workerTeamScopes).build();
}
}

View File

@ -0,0 +1,52 @@
package cn.axzo.workflow.core.deletage.approverscope;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT;
import cn.axzo.workflow.common.model.dto.CooperationOrgDTO;
import cn.axzo.workflow.common.model.dto.CooperationOrgDTO.OrgScope;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
/**
* @author syl
* @date 2023/11/21
*/
@Slf4j
@Component
public class PreTaskUserSelector implements ApproverScopeSelector {
@Override
public ApproverScopeDto select(UserTask userTask, DelegateExecution execution) {
Optional<UserTask> preUserTaskOpt = BpmnMetaParserHelper.getPreNode(userTask);
if (!preUserTaskOpt.isPresent()) {
log.info("the current node has no pre node, skip");
return ApproverScopeDto.builder().build();
}
// 上级节点
String preActivityId = preUserTaskOpt.get().getId();
// 获取上一级节点
List<BpmnTaskDelegateAssigner> assigners = (List<BpmnTaskDelegateAssigner>) execution
.getVariableLocal(INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + preActivityId);
List<OrgScope> entOrg = assigners.stream()
.map(a -> {
CooperationOrgDTO.OrgScope orgScope = new CooperationOrgDTO.OrgScope()
.setWorkspaceId(Long.valueOf(a.getTenantId()));
if (StringUtils.isNotBlank(a.getOuId())) {
orgScope.setOuId(Long.valueOf(a.getOuId()));
}
return orgScope;
})
.collect(Collectors.toList());
return ApproverScopeDto.builder().orgScopes(entOrg).build();
}
}

View File

@ -0,0 +1,44 @@
package cn.axzo.workflow.core.deletage.approverscope;
import static cn.axzo.workflow.common.constant.BpmnConstants.BIZ_ORG_RELATION;
import cn.axzo.workflow.common.enums.WorkspaceType;
import cn.axzo.workflow.common.model.dto.CooperationOrgDTO;
import cn.axzo.workflow.common.model.dto.CooperationOrgDTO.OrgScope;
import cn.hutool.core.collection.CollUtil;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
/**
* @author syl
* @date 2023/11/21
*/
@Component
public class ProjectWorkspaceSelector implements ApproverScopeSelector {
@Override
public ApproverScopeDto select(UserTask userTask, DelegateExecution execution) {
// 发起项目部
CooperationOrgDTO orgDTO =(CooperationOrgDTO) execution.getVariables().get(BIZ_ORG_RELATION);
if (Objects.isNull(orgDTO)) {
return ApproverScopeDto.builder().build();
}
List<OrgScope> orgScopes = orgDTO.getOrgScopes().stream()
.filter(o -> WorkspaceType.WORKSPACE == WorkspaceType.getType(o.getWorkspaceType()))
.collect(Collectors.toList());
List<OrgScope> workerTeamScopes = CollUtil.defaultIfEmpty(orgDTO.getWorkerTeamScopes(),
Collections.emptyList())
.stream()
.filter(o -> WorkspaceType.WORKSPACE == WorkspaceType.getType(o.getWorkspaceType()))
.collect(Collectors.toList());
return ApproverScopeDto.builder().orgScopes(orgScopes)
.workerTeamScopes(workerTeamScopes).build();
}
}

View File

@ -1,32 +1,5 @@
package cn.axzo.workflow.core.engine.listener;
import cn.axzo.workflow.common.constant.BpmnConstants;
import cn.axzo.workflow.common.enums.ApproverEmptyHandleTypeEnum;
import cn.axzo.workflow.common.enums.ApproverSpecifyEnum;
import cn.axzo.workflow.common.enums.BpmnFlowNodeType;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.core.deletage.BpmnTaskAssigneeSelector;
import cn.axzo.workflow.core.deletage.BpmnTaskCalculateDTO;
import cn.axzo.workflow.core.deletage.BpmnTaskDelegate;
import cn.axzo.workflow.core.deletage.MockTaskAssigneeSelector;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.ExecutionListener;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import static cn.axzo.workflow.common.constant.BpmnConstants.BPM_ALLOW_SKIP_USER_TASK;
import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_SERVER_VERSION_121;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO;
@ -39,6 +12,32 @@ import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApprove
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getNodeType;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getProcessServerVersion;
import cn.axzo.workflow.common.constant.BpmnConstants;
import cn.axzo.workflow.common.enums.ApproverEmptyHandleTypeEnum;
import cn.axzo.workflow.common.enums.ApproverSpecifyEnum;
import cn.axzo.workflow.common.enums.BpmnFlowNodeType;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.core.deletage.BpmnTaskAssigneeSelector;
import cn.axzo.workflow.core.deletage.BpmnTaskCalculateDTO;
import cn.axzo.workflow.core.deletage.BpmnTaskDelegate;
import cn.axzo.workflow.core.deletage.MockTaskAssigneeSelector;
import com.alibaba.fastjson.JSON;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.ExecutionListener;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* 每一个任务节点创建 Execution 时执行的监听, 用于 BPMN 文件中为节点设置指定类型监听
@ -90,7 +89,7 @@ public class EngineExecutionStartListener implements ExecutionListener {
// 这里只会是 human 这一种情况 因为 nobody 在转 BPMN 协议时Activity 直接变成了 ReceiveTask 节点了
List<BpmnTaskDelegateAssigner> assigners = new ArrayList<>();
getApproverSpecify(userTask).ifPresent(specify -> {
assigners.addAll(approverSelect(specify.getType(), userTask));
assigners.addAll(approverSelect(specify.getType(), userTask, execution));
});
// 查询审批候选人
@ -131,7 +130,7 @@ public class EngineExecutionStartListener implements ExecutionListener {
execution.setTransientVariable(BPM_ALLOW_SKIP_USER_TASK, true);
break;
case transferToAdmin:
approverSelect(ApproverEmptyHandleTypeEnum.transferToAdmin.getType(), userTask);
approverSelect(ApproverEmptyHandleTypeEnum.transferToAdmin.getType(), userTask , execution);
break;
default:
break;
@ -146,16 +145,16 @@ public class EngineExecutionStartListener implements ExecutionListener {
* @param userTask 当前节点, 这个对象会包含配置元数据, 可以在该方法中或者基于 BpmnTaskAssigneeSelector 建一个抽象类, 做解析元数据公共方法
* @return
*/
private List<BpmnTaskDelegateAssigner> approverSelect(String type, UserTask userTask) {
private List<BpmnTaskDelegateAssigner> approverSelect(String type, UserTask userTask, DelegateExecution execution) {
List<BpmnTaskDelegateAssigner> assigners = new ArrayList<>();
// 如果开启了 mock 模式workflow.mock=true, 则直接返回 mock 的审批人
if (Objects.nonNull(objectProvider.getIfAvailable())) {
objectProvider.ifAvailable(selector -> assigners.addAll(selector.select(userTask)));
objectProvider.ifAvailable(selector -> assigners.addAll(selector.select(userTask, execution)));
} else {
selectors.forEach(select -> {
if (select.support(type)) {
assigners.addAll(select.select(userTask));
assigners.addAll(select.select(userTask, execution));
}
});
}