feat(REQ-3004) - 集成表单引擎测试

This commit is contained in:
wangli 2024-11-14 18:43:32 +08:00
parent 7edfd1e0a0
commit d4e1b97a8a
30 changed files with 1063 additions and 169 deletions

View File

@ -4,9 +4,13 @@ import cn.axzo.workflow.client.annotation.WorkflowEngineFeignClient;
import cn.axzo.workflow.common.annotation.InvokeMode;
import cn.axzo.workflow.common.annotation.Manageable;
import cn.axzo.workflow.common.model.request.form.definition.FormDefinitionSearchDTO;
import cn.axzo.workflow.common.model.request.form.definition.StartFormSearchDTO;
import cn.axzo.workflow.common.model.request.form.instance.FormDetailDTO;
import cn.axzo.workflow.common.model.request.form.instance.FormInstanceSearchDTO;
import cn.axzo.workflow.common.model.request.form.instance.FormSearchDTO;
import cn.axzo.workflow.common.model.response.form.FormVO;
import cn.axzo.workflow.common.model.response.form.definition.FormDefinitionVO;
import cn.axzo.workflow.common.model.response.form.instance.FormInstanceVO;
import cn.azxo.framework.common.model.CommonResponse;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
@ -30,7 +34,11 @@ public interface FormInstanceApi {
@InvokeMode(SYNC)
CommonResponse<List<FormVO>> formPage(@Validated @RequestBody FormSearchDTO dto);
@PostMapping("/api/form/instance/init")
@PostMapping("/api/form/instance/start/form")
@InvokeMode(SYNC)
CommonResponse<FormDefinitionVO> getFormDefinition(@Validated @RequestBody FormDefinitionSearchDTO dto);
CommonResponse<FormDefinitionVO> getFormDefinition(@Validated @RequestBody StartFormSearchDTO dto);
@PostMapping("/api/form/instance/get")
@InvokeMode(SYNC)
CommonResponse<FormInstanceVO> getFormInstance(@Validated @RequestBody FormDetailDTO dto);
}

View File

@ -0,0 +1,45 @@
package cn.axzo.workflow.common.code;
import cn.axzo.framework.domain.web.code.IModuleRespCode;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 表单实例相关响应码
*
* @author wangli
* @since 2024-11-13 14:34
*/
@Getter
@AllArgsConstructor
public enum FormInstanceRespCode implements IModuleRespCode {
FORM_PARAM_ERROR("001", "流程实例 ID 和任务 ID 不能都为空"),
FORM_FIELD_NOT_FOUND("002", "无法获取全量表单字段权限配置信息"),
;
private final String code;
private final String message;
@Override
public String getModuleCode() {
return "13";
}
@Override
public String getProjectCode() {
return "998";
}
@Override
public String getCode() {
return code;
}
@Override
public String getMessage() {
return message;
}
}

View File

@ -7,5 +7,14 @@ package cn.axzo.workflow.common.constant;
* @since 2024-11-05 10:20
*/
public interface FormConstants {
String DRAFT_CATEGORY = "draft";
String FIELD_PROPERTY_REQUIRED = "required";
String FIELD_PROPERTY_EDITABLE = "editable";
String FIELD_PROPERTY_READONLY = "readonly";
String FIELD_PROPERTY_HIDDEN = "hidden";
String FIELD_PROPERTY_DEFAULT_VALUE= "defaultValue";
}

View File

@ -1,60 +1,60 @@
package cn.axzo.workflow.common.model.request.form;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.List;
/**
* 表单字段权限配置
* <p>
* 发起人
* 只读 可编辑必填 可编辑非必填 隐藏
* 1 1 1 1 字段 1
* 1 0 0 1 字段 2
*
* @author wangli
* @since 2024-11-06 18:19
*/
@ApiModel("JSON 版本的 BPMN 协议模型中的表单字段权限")
@Data
@NoArgsConstructor
@Accessors(chain = true)
public class FormFieldPermissionConf implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 发起人的表单字段权限
*/
@ApiModelProperty(value = "发起人的表单字段权限")
private List<FormPermissionMetaInfo> initiator;
/**
* 审批人的表单字段权限
*/
@ApiModelProperty(value = "审批人的表单字段权限")
private List<FormPermissionMetaInfo> current;
/**
* 历史审批人的表单字段权限
*/
@ApiModelProperty(value = "历史审批人的表单字段权限")
private List<FormPermissionMetaInfo> history;
/**
* 抄送人的表单字段权限
*/
@ApiModelProperty(value = "抄送人的表单字段权限")
private List<FormPermissionMetaInfo> carbonCopy;
/**
* 管理员的字段权限
*/
@ApiModelProperty(value = "管理员的字段权限")
private List<FormPermissionMetaInfo> admin;
}
//package cn.axzo.workflow.common.model.request.form;
//
//import io.swagger.annotations.ApiModel;
//import io.swagger.annotations.ApiModelProperty;
//import lombok.Data;
//import lombok.NoArgsConstructor;
//import lombok.experimental.Accessors;
//
//import java.io.Serializable;
//import java.util.List;
//
///**
// * 表单字段权限配置
// * <p>
// * 发起人
// * 只读 可编辑必填 可编辑非必填 隐藏
// * 1 1 1 1 字段 1
// * 1 0 0 1 字段 2
// *
// * @author wangli
// * @since 2024-11-06 18:19
// */
//@ApiModel("JSON 版本的 BPMN 协议模型中的表单字段权限")
//@Data
//@NoArgsConstructor
//@Accessors(chain = true)
//public class FormFieldPermissionConf implements Serializable {
// private static final long serialVersionUID = 1L;
//
// /**
// * 发起人的表单字段权限
// */
// @ApiModelProperty(value = "发起人的表单字段权限")
// private List<FormPermissionMetaInfo> initiator;
//
// /**
// * 审批人的表单字段权限
// */
// @ApiModelProperty(value = "审批人的表单字段权限")
// private List<FormPermissionMetaInfo> current;
//
// /**
// * 历史审批人的表单字段权限
// */
// @ApiModelProperty(value = "历史审批人的表单字段权限")
// private List<FormPermissionMetaInfo> history;
//
// /**
// * 抄送人的表单字段权限
// */
// @ApiModelProperty(value = "抄送人的表单字段权限")
// private List<FormPermissionMetaInfo> carbonCopy;
//
// /**
// * 管理员的字段权限
// */
// @ApiModelProperty(value = "管理员的字段权限")
// private List<FormPermissionMetaInfo> admin;
//
//}

View File

@ -1,6 +1,9 @@
package cn.axzo.workflow.common.model.request.form;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
@ -12,7 +15,9 @@ import java.io.Serializable;
* @since 2024-11-07 11:09
*/
@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class FormPermissionMetaInfo implements Serializable {
private static final long serialVersionUID = 1L;
@ -29,7 +34,7 @@ public class FormPermissionMetaInfo implements Serializable {
/**
* 可编辑非必填
*/
private Boolean editable = true;
private Boolean editable = false;
/**
* 只读
@ -39,5 +44,28 @@ public class FormPermissionMetaInfo implements Serializable {
/**
* 隐藏
*/
private Boolean hidden = false;
private Boolean hidden = true;
// 将对象的属性转换为对应的整数表示
public int toBinary() {
int binaryValue = 0;
binaryValue |= (editable? 1 : 0) << 3;
binaryValue |= (required? 1 : 0) << 2;
binaryValue |= (readonly? 1 : 0) << 1;
binaryValue |= (hidden? 1 : 0);
return binaryValue;
}
// 从整数表示还原出对象
public static FormPermissionMetaInfo fromBinary(String fieldId, int binaryValue) {
boolean editable = ((binaryValue >> 3) & 1) == 1;
boolean required = ((binaryValue >> 2) & 1) == 1;
boolean readonly = ((binaryValue >> 1) & 1) == 1;
boolean hidden = (binaryValue & 1) == 1;
return new FormPermissionMetaInfo(fieldId, editable, required, readonly, hidden);
}
public String toBinaryString() {
return String.format("%04d", Integer.parseInt(Integer.toBinaryString(toBinary()), 10));
}
}

View File

@ -1,42 +0,0 @@
package cn.axzo.workflow.common.model.request.form.definition;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.Valid;
import static cn.axzo.workflow.common.constant.BpmnConstants.NO_TENANT_ID;
/**
* 表单定义的草稿模型
*
* @author wangli
* @since 2024-11-05 10:11
*/
@Data
public class FormDraftDefinitionDTO {
/**
* 模型的 KEY
*/
@ApiModelProperty(value = "模型的 KEY")
private String key;
/**
* 模型的分类
*/
@ApiModelProperty(value = "模型的分类")
private String category;
/**
* 租户 ID
*/
@ApiModelProperty(value = "租户 ID")
private String tenantId = NO_TENANT_ID;
/**
* 表单定义
*/
@ApiModelProperty(value = "表单定义内容")
@Valid
private FormDefinitionDTO formDefinition;
}

View File

@ -0,0 +1,31 @@
package cn.axzo.workflow.common.model.request.form.definition;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import static cn.axzo.workflow.common.constant.BpmnConstants.NO_TENANT_ID;
/**
* 获取开始表单的模型入参
*
* @author wangli
* @since 2024-11-14 16:27
*/
@Data
@ApiModel("获取开始表单的模型入参")
public class StartFormSearchDTO {
/**
* 业务标识
*/
@ApiModelProperty(value = "业务标识", notes = "也等于表单唯一标识,目前新建一个审批模型,表单和审批的标识都是一样的")
@NotBlank(message = "业务标识不能为空")
private String key;
/**
* 租户 ID
*/
private String tenantId = NO_TENANT_ID;
}

View File

@ -0,0 +1,39 @@
package cn.axzo.workflow.common.model.request.form.instance;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 表单详情响应模型
*
* @author wangli
* @since 2024-11-14 14:22
*/
@ApiModel("表单详情响应模型")
@Data
public class FormDetailDTO {
/**
* 流程实例 ID
*/
@ApiModelProperty(value = "流程实例ID")
@NotBlank(message = "流程实例 ID 不能为空")
private String processInstanceId;
/**
* 任务 ID
*/
@ApiModelProperty(value = "任务 ID")
private String taskId;
/**
* 访问表单的人
*/
@ApiModelProperty(value = "访问表单的人")
private BpmnTaskDelegateAssigner assigner;
}

View File

@ -4,6 +4,7 @@ import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
import java.util.Map;
@ -29,6 +30,7 @@ public class FormInstanceSearchDTO implements Serializable {
* 实例 ID
*/
@ApiModelProperty(value = "实例 ID")
@NotBlank(message = "流程实例不能为空")
private String processInstanceId;
/**

View File

@ -0,0 +1,31 @@
package cn.axzo.workflow.common.model.response.form.instance;
import cn.axzo.workflow.common.model.response.form.definition.FormDefinitionVO;
import cn.axzo.workflow.common.model.response.form.model.FormModelVO;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import java.util.Date;
/**
* 表单实例的响应模型
*
* @author wangli
* @since 2024-11-14 10:05
*/
@ApiModel("表单实例的响应模型")
@Data
public class FormInstanceVO {
private String id;
private String name;
private String key;
private Integer version;
private FormModelVO formModel;
private String formInstanceId;
private String submittedBy;
private Date submittedDate;
private String taskId;
private String processInstanceId;
private String processDefinitionId;
private String tenantId;
}

View File

@ -0,0 +1,23 @@
package cn.axzo.workflow.common.model.response.form.model;
import cn.axzo.workflow.common.model.request.form.definition.FormFieldDTO;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import java.util.List;
/**
* 表单模型响应模型
*
* @author wangli
* @since 2024-11-14 10:10
*/
@ApiModel("表单模型响应模型")
@Data
public class FormModelVO {
private String id;
private String name;
private String key;
private Integer version;
private List<FormFieldDTO> fields;
}

View File

@ -1,6 +1,7 @@
package cn.axzo.workflow.core.common.utils;
import cn.axzo.workflow.common.enums.ApprovalMethodEnum;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.request.BpmnApproveConf;
import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonConf;
import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonMetaInfo;
@ -9,8 +10,7 @@ import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonModel;
import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonNode;
import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeConf;
import cn.axzo.workflow.common.model.request.bpmn.model.BpmnModelCreateDTO;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.request.form.FormFieldPermissionConf;
import cn.axzo.workflow.common.model.request.form.FormPermissionMetaInfo;
import cn.axzo.workflow.core.converter.json.AbstractBpmnJsonConverter;
import cn.axzo.workflow.core.converter.json.BoundaryEventJsonConverter;
import cn.axzo.workflow.core.converter.json.EndEventJsonConverter;
@ -71,6 +71,7 @@ import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import static cn.axzo.workflow.common.code.ConvertorRespCode.CONVERTOR_COMMON_ERROR;
import static cn.axzo.workflow.common.constant.BpmnConstants.APPROVE_SUPPORT_BATCH_OPERATION;
import static cn.axzo.workflow.common.constant.BpmnConstants.APPROVE_USER_AGREE_SIGNATURE;
import static cn.axzo.workflow.common.constant.BpmnConstants.AUTO_APPROVAL_TYPE;
@ -112,7 +113,6 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_SMS_MESSAG
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_CONDITION;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_EMPTY;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_EXCLUSIVE_GATEWAY;
import static cn.axzo.workflow.common.code.ConvertorRespCode.CONVERTOR_COMMON_ERROR;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getButtonConfig;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getFieldConfig;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getNoticeConfig;
@ -765,8 +765,8 @@ public final class BpmnJsonConverterUtil {
// FlowElement flowElement = bpmnModel.getFlowElement("node_350687681316");
FlowElement flowElement = bpmnModel.getFlowElement("node_429076682717");
Optional<FormFieldPermissionConf> formFieldPermissionConf =
BpmnMetaParserHelper.getFormFieldPermissionConf(flowElement);
Optional<List<FormPermissionMetaInfo>> formFieldPermissionConf
= BpmnMetaParserHelper.getFormFieldPermissionConf(flowElement);
// ServiceTask serviceTask = (ServiceTask) bpmnModel.getFlowElement("node_946990365785");
// Optional<List<BpmnCarbonCopyConf>> carbonCopyConfigs = BpmnMetaParserHelper.getCarbonCopyConfigs
// (serviceTask);

View File

@ -20,9 +20,9 @@ import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeProperty;
import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeReceiver;
import cn.axzo.workflow.common.model.request.bpmn.BpmnPendingProperty;
import cn.axzo.workflow.common.model.request.bpmn.BpmnSmsProperty;
import cn.axzo.workflow.common.model.request.form.FormFieldPermissionConf;
import cn.axzo.workflow.common.model.request.form.FormPermissionMetaInfo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.ExtensionElement;
import org.flowable.bpmn.model.FlowElement;
@ -38,10 +38,13 @@ import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;
import java.util.stream.Collectors;
import static cn.axzo.workflow.common.constant.BpmnConstants.APPROVE_SUPPORT_BATCH_OPERATION;
import static cn.axzo.workflow.common.constant.BpmnConstants.APPROVE_USER_AGREE_SIGNATURE;
@ -427,17 +430,90 @@ public final class BpmnMetaParserHelper {
return Optional.empty();
}
public static Optional<FormFieldPermissionConf> getFormFieldPermissionConf(FlowElement flowElement) {
return defaultValid(flowElement, CONFIG_FIELD_PERMISSION).map(element-> JSON.parseObject(element.getElementText(), FormFieldPermissionConf.class));
public static Optional<List<FormPermissionMetaInfo>> getFormFieldPermissionConf(FlowElement flowElement) {
return defaultValid(flowElement, CONFIG_FIELD_PERMISSION).map(element -> JSON.parseObject(element.getElementText(), new TypeReference<List<FormPermissionMetaInfo>>() {
}.getType()));
}
public static Optional<Map<String, Integer>> getFormFieldPermissionForCalc(FlowElement flowElement) {
FormFieldPermissionConf formFieldPermissionConf = getFormFieldPermissionConf(flowElement).orElse(null);
if(Objects.nonNull(formFieldPermissionConf)) {
// formFieldPermissionConf.getCurrent()
return Optional.of(null);
List<FormPermissionMetaInfo> fieldMetaInfos = getFormFieldPermissionConf(flowElement).orElse(new ArrayList<>());
return getFormFieldPermissionForModel(fieldMetaInfos);
}
public static Optional<Map<String, Integer>> getFormFieldPermissionForModel(List<FormPermissionMetaInfo> fieldMetaInfos) {
if (CollectionUtils.isEmpty(fieldMetaInfos)) {
return Optional.empty();
}
return Optional.empty();
Map<String, Integer> result = fieldMetaInfos.stream()
.collect(Collectors.toMap(FormPermissionMetaInfo::getFieldId,
FormPermissionMetaInfo::toBinary,
(s, t) -> s));
return Optional.of(result);
}
public static List<FormPermissionMetaInfo> mergeAllPermission(List<FormPermissionMetaInfo>... permissions) {
if (permissions.length == 0) {
return Collections.emptyList();
}
List<Map<String, Integer>> combinedMap = new ArrayList<>();
for (List<FormPermissionMetaInfo> permission : permissions) {
combinedMap.add(getFormFieldPermissionForModel(permission).orElse(new HashMap<>()));
}
Map<String, List<Integer>> mergeMap = new HashMap<>();
for (Map<String, Integer> map : combinedMap) {
for (Map.Entry<String, Integer> entry : map.entrySet()) {
mergeMap.putIfAbsent(entry.getKey(), new ArrayList<>());
mergeMap.get(entry.getKey()).add(entry.getValue());
}
}
List<FormPermissionMetaInfo> permissionResult = new ArrayList<>();
for (Map.Entry<String, List<Integer>> entry : mergeMap.entrySet()) {
int[] array = entry.getValue().stream().mapToInt(Integer::intValue).toArray();
int result = performBitwiseOr(array);
permissionResult.add(FormPermissionMetaInfo.fromBinary(entry.getKey(), result));
}
return permissionResult;
}
private static int performBitwiseOr(int... numbers) {
int result = 0;
for (int num : numbers) {
result |= num;
}
return result;
}
// just for testing
private static List<FormPermissionMetaInfo> genericConf(int num) {
List<FormPermissionMetaInfo> fields = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < num; i++) {
String fieldId = "field_" + i;
FormPermissionMetaInfo metaInfo = FormPermissionMetaInfo
.builder()
.fieldId(fieldId)
.editable(random.nextBoolean())
.required(random.nextBoolean())
.readonly(random.nextBoolean())
.hidden(random.nextBoolean())
.build();
fields.add(metaInfo);
}
return fields;
}
public static void main(String[] args) {
List<FormPermissionMetaInfo> conf1 = genericConf(5);
conf1.forEach(e -> System.out.println("conf1 ---> e.fieldId = " + e.getFieldId() + ",e.toBinary() = " + e.toBinary() + ",e.toBinaryString() = " + e.toBinaryString()));
List<FormPermissionMetaInfo> conf2 = genericConf(5);
conf2.forEach(e -> System.out.println("conf2 ---> e.fieldId = " + e.getFieldId() + ",e.toBinary() = " + e.toBinary() + ",e.toBinaryString() = " + e.toBinaryString()));
List<FormPermissionMetaInfo> result = mergeAllPermission(conf1, conf2);
result.forEach(e -> System.out.println("result ---> e.fieldId = " + e.getFieldId() + ",e.toBinary() = " + e.toBinary() + ",e.toBinaryString() = " + e.toBinaryString()));
}
}

View File

@ -0,0 +1,58 @@
package cn.axzo.workflow.core.conf.handler;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.common.model.request.form.FormPermissionMetaInfo;
import com.baomidou.mybatisplus.core.toolkit.Assert;
import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import java.io.IOException;
import java.util.List;
/**
* BpmnTaskDelegateAssigner 数据映射转换
*
* @author wangli
* @since 2024-09-07 22:40
*/
@Slf4j
@MappedTypes({List.class})
@MappedJdbcTypes({JdbcType.VARCHAR})
public class ListFormFieldPermissionTypeHandler extends AbstractJsonTypeHandler<List<FormPermissionMetaInfo>> {
private static ObjectMapper objectMapper = new ObjectMapper();
public ListFormFieldPermissionTypeHandler(Class<?> type) {
if (log.isTraceEnabled()) {
log.trace("JacksonTypeHandler(" + type + ")");
}
Assert.notNull(type, "Type argument cannot be null", new Object[0]);
}
protected List<FormPermissionMetaInfo> parse(String json) {
try {
// 这里进行了json解析同样在这里也可以进行字段查询后的处理如对象内部的手机号字段的加密展示等
return objectMapper.readValue(json, new TypeReference<List<FormPermissionMetaInfo>>() {
});
} catch (IOException var3) {
throw new RuntimeException(var3);
}
}
protected String toJson(List<FormPermissionMetaInfo> obj) {
try {
return objectMapper.writeValueAsString(obj);
} catch (JsonProcessingException var3) {
throw new RuntimeException(var3);
}
}
public static void setObjectMapper(ObjectMapper om) {
objectMapper = om;
}
}

View File

@ -0,0 +1,252 @@
package cn.axzo.workflow.core.engine.cmd;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.common.model.request.form.FormPermissionMetaInfo;
import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper;
import cn.axzo.workflow.core.repository.entity.ExtAxBpmnFormRelation;
import cn.axzo.workflow.core.repository.entity.ExtAxProcessLog;
import cn.axzo.workflow.core.service.BpmnProcessTaskForEsService;
import cn.axzo.workflow.core.service.ExtAxBpmnFormRelationService;
import lombok.extern.slf4j.Slf4j;
import org.flowable.common.engine.api.delegate.Expression;
import org.flowable.common.engine.impl.el.ExpressionManager;
import org.flowable.common.engine.impl.el.VariableContainerWrapper;
import org.flowable.common.engine.impl.interceptor.Command;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.HistoryService;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.form.api.FormDefinition;
import org.flowable.form.api.FormEngineConfigurationApi;
import org.flowable.form.api.FormInstanceInfo;
import org.flowable.form.api.FormRepositoryService;
import org.flowable.form.api.FormService;
import org.flowable.form.model.FormField;
import org.flowable.form.model.SimpleFormModel;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_ID_NOT_EXISTS;
import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TASK_COMPLETE_FAIL_NOT_EXISTS;
import static cn.axzo.workflow.common.code.FormInstanceRespCode.FORM_FIELD_NOT_FOUND;
import static cn.axzo.workflow.common.code.FormInstanceRespCode.FORM_PARAM_ERROR;
import static cn.axzo.workflow.common.constant.FormConstants.FIELD_PROPERTY_DEFAULT_VALUE;
import static cn.axzo.workflow.common.constant.FormConstants.FIELD_PROPERTY_EDITABLE;
import static cn.axzo.workflow.common.constant.FormConstants.FIELD_PROPERTY_HIDDEN;
import static cn.axzo.workflow.common.constant.FormConstants.FIELD_PROPERTY_READONLY;
import static cn.axzo.workflow.common.constant.FormConstants.FIELD_PROPERTY_REQUIRED;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCESSING;
/**
* 获取指定审批实例的表单内容以及字段权限
*
* @author wangli
* @since 2024-11-13 14:24
*/
@Slf4j
public class GetFormInstanceAndPermissionCmd implements Command<FormInstanceInfo> {
private final ExtAxBpmnFormRelationService bpmnFormRelationService;
private final BpmnProcessTaskForEsService bpmnProcessTaskForEsService;
private final BpmnTaskDelegateAssigner assigner;
private String processInstanceId;
private String taskId;
/**
* 全权通过实例查询一切
*
* @param processInstanceId
*/
public GetFormInstanceAndPermissionCmd(ExtAxBpmnFormRelationService bpmnFormRelationService,
BpmnProcessTaskForEsService bpmnProcessTaskForEsService,
BpmnTaskDelegateAssigner assigner,
String processInstanceId) {
this.bpmnFormRelationService = bpmnFormRelationService;
this.bpmnProcessTaskForEsService = bpmnProcessTaskForEsService;
this.assigner = assigner;
this.processInstanceId = processInstanceId;
}
/**
* 通过实例和任务查询
*
* @param processInstanceId 可为空,则用 taskId 反查实例相关
* @param taskId
*/
public GetFormInstanceAndPermissionCmd(ExtAxBpmnFormRelationService bpmnFormRelationService,
BpmnProcessTaskForEsService bpmnProcessTaskForEsService,
BpmnTaskDelegateAssigner assigner,
String processInstanceId,
String taskId) {
this.bpmnFormRelationService = bpmnFormRelationService;
this.bpmnProcessTaskForEsService = bpmnProcessTaskForEsService;
this.assigner = assigner;
this.processInstanceId = processInstanceId;
this.taskId = taskId;
}
@Override
public FormInstanceInfo execute(CommandContext commandContext) {
preCheckParam();
// 获取表单定义+字段内容
FormInstanceInfo formInstanceInfo = getFormInstanceInfo(commandContext);
if (Objects.isNull(formInstanceInfo)) {
return null;
}
// 获取字段权限
List<FormPermissionMetaInfo> fieldPermission = getFieldPermission(commandContext);
Map<String, FormPermissionMetaInfo> fieldMap = fieldPermission.stream()
.collect(Collectors.toMap(FormPermissionMetaInfo::getFieldId, Function.identity(), (s, t) -> s));
SimpleFormModel formModel = (SimpleFormModel) formInstanceInfo.getFormModel();
Map<String, FormField> flatField = formModel.allFieldsAsMap();
ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext);
HistoryService historyService = processEngineConfiguration.getHistoryService();
ExpressionManager expressionManager = processEngineConfiguration.getExpressionManager();
HistoricProcessInstance instance = historyService.createHistoricProcessInstanceQuery()
.processInstanceId(processInstanceId)
.includeProcessVariables()
.singleResult();
Map<String, Object> processVariables = instance.getProcessVariables();
VariableContainerWrapper variableContainerWrapper = new VariableContainerWrapper(processVariables);
flatField.forEach((fieldId, formField) -> {
Map<String, Object> oldParams = CollectionUtils.isEmpty(formField.getParams()) ? new HashMap<>() : formField.getParams();
FormPermissionMetaInfo defaultPermission = fieldMap.getOrDefault(fieldId, new FormPermissionMetaInfo());
oldParams.put(FIELD_PROPERTY_REQUIRED, defaultPermission.getRequired());
oldParams.put(FIELD_PROPERTY_EDITABLE, defaultPermission.getEditable());
oldParams.put(FIELD_PROPERTY_READONLY, defaultPermission.getReadonly());
oldParams.put(FIELD_PROPERTY_HIDDEN, defaultPermission.getHidden());
if (oldParams.containsKey(FIELD_PROPERTY_DEFAULT_VALUE)) {
Expression expression = expressionManager.createExpression(String.valueOf(oldParams.getOrDefault(FIELD_PROPERTY_DEFAULT_VALUE, "")));
Object value = expression.getValue(variableContainerWrapper);
oldParams.put(FIELD_PROPERTY_DEFAULT_VALUE, Objects.nonNull(value) ? String.valueOf(value) : null);
}
});
return formInstanceInfo;
}
private List<FormPermissionMetaInfo> getFieldPermission(CommandContext commandContext) {
List<ExtAxProcessLog> logs = bpmnProcessTaskForEsService.queryProcessLogByProcessInstanceId(processInstanceId);
Map<String, ExtAxProcessLog> activityMap = logs.stream()
.collect(Collectors.toMap(ExtAxProcessLog::getActivityId, Function.identity(), (s, t) -> s));
// 待审批的节点
List<ExtAxProcessLog> processingLogs = logs.stream()
.filter(e -> Objects.equals(PROCESSING.getStatus(), e.getStatus()) && Objects.isNull(e.getEndTime()))
.collect(Collectors.toList());
List<FormPermissionMetaInfo> list = binaryMerge(processingLogs, activityMap);
if (!CollectionUtils.isEmpty(list)) {
return list;
}
// 已结束的审批节点
List<ExtAxProcessLog> finishLogs = logs.stream()
.filter(e -> !Objects.equals(PROCESSING.getStatus(), e.getStatus()) && Objects.nonNull(e.getEndTime()))
.collect(Collectors.toList());
list = binaryMerge(finishLogs, activityMap);
if (!CollectionUtils.isEmpty(list)) {
return list;
}
// 默认全字段都是隐藏
ExtAxProcessLog starterLog = logs.stream().filter(e -> Objects.equals(NODE_STARTER.getType(), e.getActivityId()))
.findFirst().orElseThrow(() -> new WorkflowEngineException(FORM_FIELD_NOT_FOUND));
return CollectionUtils.isEmpty(starterLog.getFormFieldPermissionConf()) ? new ArrayList<>() : starterLog.getFormFieldPermissionConf();
}
private List<FormPermissionMetaInfo> binaryMerge(List<ExtAxProcessLog> logs, Map<String, ExtAxProcessLog> activityMap) {
List<List<FormPermissionMetaInfo>> permissions = new ArrayList<>();
collectActivityIds(logs).forEach(id -> {
ExtAxProcessLog log = activityMap.getOrDefault(id, null);
if (Objects.nonNull(log)) {
permissions.add(log.getFormFieldPermissionConf());
}
});
return BpmnMetaParserHelper.mergeAllPermission(permissions.toArray(new List[0]));
}
private List<String> collectActivityIds(List<ExtAxProcessLog> logs) {
List<String> activityIds = new ArrayList<>();
if (!CollectionUtils.isEmpty(logs)) {
Map<String, List<BpmnTaskDelegateAssigner>> nodeUserMap = rebuildToNodeUserMap(logs);
nodeUserMap.forEach((activityId, assigners) -> {
assigners.forEach(u -> {
if (Objects.equals(u.getPersonId(), assigner.getPersonId())) {
activityIds.add(activityId);
}
});
});
}
return activityIds;
}
private static Map<String, List<BpmnTaskDelegateAssigner>> rebuildToNodeUserMap(List<ExtAxProcessLog> logs) {
Map<String, List<BpmnTaskDelegateAssigner>> assignerMap = new HashMap<>();
logs.forEach(e -> {
if (assignerMap.containsKey(e.getActivityId())) {
assignerMap.get(e.getActivityId()).addAll(e.getAssigneeFull());
} else {
assignerMap.put(e.getActivityId(), new ArrayList<>());
}
});
return assignerMap;
}
private FormInstanceInfo getFormInstanceInfo(CommandContext commandContext) {
ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext);
HistoryService historyService = processEngineConfiguration.getHistoryService();
HistoricProcessInstance instance;
if (StringUtils.hasText(processInstanceId)) {
instance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
} else {
HistoricTaskInstance task = historyService.createHistoricTaskInstanceQuery().taskId(taskId).singleResult();
if (Objects.isNull(task)) {
throw new WorkflowEngineException(TASK_COMPLETE_FAIL_NOT_EXISTS);
}
instance = historyService.createHistoricProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
}
if (Objects.isNull(instance)) {
throw new WorkflowEngineException(PROCESS_INSTANCE_ID_NOT_EXISTS, processInstanceId);
}
processInstanceId = instance.getId();
ExtAxBpmnFormRelation relation = bpmnFormRelationService.queryByBpmnDefinitionId(instance.getProcessDefinitionId());
if (Objects.isNull(relation)) {
// 没有配置表单
return null;
}
FormEngineConfigurationApi formEngineConfiguration = CommandContextUtil.getFormEngineConfiguration(commandContext);
FormRepositoryService formRepositoryService = formEngineConfiguration.getFormRepositoryService();
FormDefinition formDefinition = formRepositoryService.createFormDefinitionQuery().deploymentId(relation.getFormDeploymentId()).singleResult();
FormService formService = formEngineConfiguration.getFormService();
// 获取表单定义+字段内容
return formService.getFormInstanceModelById(formDefinition.getId(), taskId, processInstanceId, null, instance.getTenantId(), false);
}
private void preCheckParam() {
if (!StringUtils.hasText(processInstanceId) && !StringUtils.hasText(taskId)) {
throw new WorkflowEngineException(FORM_PARAM_ERROR);
}
}
}

View File

@ -13,6 +13,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.ListUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.impl.bpmn.behavior.MultiInstanceActivityBehavior;
@ -98,16 +99,16 @@ public class TaskEntityEventHandle implements EntityEventHandle<TaskEntity> {
@Override
public void onInitialized(TaskEntity taskEntity) {
BpmnMetaParserHelper.getButtonConfig(ProcessDefinitionUtil.getProcess(taskEntity.getProcessDefinitionId()), taskEntity.getTaskDefinitionKey())
.ifPresent(buttons -> {
ExtAxProcessLog queryLog = new ExtAxProcessLog();
queryLog.setProcessInstanceId(taskEntity.getProcessInstanceId());
queryLog.setTaskId(taskEntity.getId());
ExtAxProcessLog updateLog = new ExtAxProcessLog();
updateLog.setButtonConf(buttons);
processLogService.update(queryLog, updateLog);
});
Process process = ProcessDefinitionUtil.getProcess(taskEntity.getProcessDefinitionId());
ExtAxProcessLog queryLog = new ExtAxProcessLog();
queryLog.setProcessInstanceId(taskEntity.getProcessInstanceId());
queryLog.setTaskId(taskEntity.getId());
ExtAxProcessLog updateLog = new ExtAxProcessLog();
BpmnMetaParserHelper.getButtonConfig(process, taskEntity.getTaskDefinitionKey())
.ifPresent(updateLog::setButtonConf);
BpmnMetaParserHelper.getFormFieldPermissionConf(process.getFlowElement(taskEntity.getTaskDefinitionKey()))
.ifPresent(updateLog::setFormFieldPermissionConf);
processLogService.update(queryLog, updateLog);
}
@Override
@ -162,9 +163,13 @@ public class TaskEntityEventHandle implements EntityEventHandle<TaskEntity> {
}
Object operationDesc = taskEntity.getTransientVariableLocal(COMMENT_TYPE_OPERATION_DESC);
if (Objects.nonNull(operationDesc) && StringUtils.hasText(operationDesc.toString())) {
update.setOperationDesc(Objects.nonNull(assignee) ? assignee.getAssignerName() + operationDesc : operationDesc.toString());
update.setOperationDesc(Objects.nonNull(assignee) ?
(StringUtils.hasText(assignee.getAssignerName()) ? assignee.getAssignerName() : "") + operationDesc
: operationDesc.toString());
} else {
update.setOperationDesc(Objects.nonNull(assignee) ? assignee.getAssignerName() : "");
update.setOperationDesc(Objects.nonNull(assignee) ?
(StringUtils.hasText(assignee.getAssignerName()) ? assignee.getAssignerName() : "")
: "");
}
@ -212,7 +217,7 @@ public class TaskEntityEventHandle implements EntityEventHandle<TaskEntity> {
UserTask userTask = (UserTask) flowElement;
if (userTask.getBehavior() instanceof MultiInstanceActivityBehavior) {
MultiInstanceActivityBehavior behavior =
(MultiInstanceActivityBehavior) userTask.getBehavior();
(MultiInstanceActivityBehavior) userTask.getBehavior();
node = Objects.equals(AND_SIGN_EXPRESSION, behavior.getCompletionCondition()) ? AND : OR;
}
}

View File

@ -3,8 +3,10 @@ package cn.axzo.workflow.core.repository.entity;
import cn.axzo.framework.data.mybatisplus.model.BaseEntity;
import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonConf;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.common.model.request.form.FormPermissionMetaInfo;
import cn.axzo.workflow.core.conf.handler.ButtonConfTypeHandler;
import cn.axzo.workflow.core.conf.handler.ListAssigneeTypeHandler;
import cn.axzo.workflow.core.conf.handler.ListFormFieldPermissionTypeHandler;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
@ -102,6 +104,11 @@ public class ExtAxProcessLog extends BaseEntity<ExtAxProcessLog> {
*/
@TableField(typeHandler = ButtonConfTypeHandler.class)
private BpmnButtonConf buttonConf;
/**
* 表单字段权限配置
*/
@TableField(typeHandler = ListFormFieldPermissionTypeHandler.class)
private List<FormPermissionMetaInfo> formFieldPermissionConf;
/**
* 任务状态:审批中/通过/驳回/转交/加签/回退
*/

View File

@ -0,0 +1,27 @@
package cn.axzo.workflow.core.service;
import cn.axzo.workflow.common.model.dto.BpmnFormRelationSearchDTO;
import cn.axzo.workflow.common.model.request.form.definition.FormDefinitionSearchDTO;
import cn.axzo.workflow.common.model.request.form.definition.StartFormSearchDTO;
import cn.axzo.workflow.common.model.request.form.instance.FormDetailDTO;
import cn.axzo.workflow.common.model.request.form.instance.FormInstanceSearchDTO;
import cn.axzo.workflow.common.model.response.form.FormVO;
import cn.axzo.workflow.common.model.response.form.definition.FormDefinitionVO;
import cn.axzo.workflow.common.model.response.form.instance.FormInstanceVO;
import java.util.List;
/**
* 由于 POM 的结构的问题,这个类主要解决循环依赖的问题
*
* @author wangli
* @since 2024-11-14 10:17
*/
public interface FormCoreService {
List<FormVO> pageForm(BpmnFormRelationSearchDTO dto);
FormDefinitionVO getStartForm(StartFormSearchDTO dto);
FormInstanceVO getFormInstance(FormDetailDTO dto);
}

View File

@ -16,6 +16,7 @@ import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAdm
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCancelDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCarbonCopyDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCheckApproverDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCreateDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCreateWithFormDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceLogQueryDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceMyPageReqVO;
@ -375,7 +376,7 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic
if (Objects.isNull(relation)) {
// 如果模型没有绑定表单则强制情况表单相关属性避免报错
instanceBuilder.startFormVariables(null).outcome(null);
}else {
} else {
instanceBuilder.startFormVariables(dto.getStartFormVariables())
.outcome(dto.getOutcome());
}

View File

@ -348,6 +348,7 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
@Override
@Transactional(rollbackFor = Exception.class)
public void approveTaskWithForm(BpmnTaskAuditWithFormDTO dto) {
// 表单级别暂时只允许同步
CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor();
commandExecutor.execute(new CustomApproveTaskWithFormCmd(dto));
}

View File

@ -79,6 +79,7 @@ public class ExtAxBpmnFormRelationServiceImpl implements ExtAxBpmnFormRelationSe
.eq(StringUtils.hasText(dto.getFormDeploymentId()), ExtAxBpmnFormRelation::getFormDeploymentId, dto.getFormDeploymentId())
.eq(!StringUtils.hasText(dto.getTenantId()), ExtAxBpmnFormRelation::getTenantId, NO_TENANT_ID)
.eq(StringUtils.hasText(dto.getTenantId()), ExtAxBpmnFormRelation::getTenantId, dto.getTenantId())
.eq(ExtAxBpmnFormRelation::getIsDelete, 0)
;
}
}

View File

@ -0,0 +1,134 @@
package cn.axzo.workflow.core.service.impl;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.dto.BpmnFormRelationSearchDTO;
import cn.axzo.workflow.common.model.request.form.FormPermissionMetaInfo;
import cn.axzo.workflow.common.model.request.form.definition.StartFormSearchDTO;
import cn.axzo.workflow.common.model.request.form.instance.FormDetailDTO;
import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessDefinitionVO;
import cn.axzo.workflow.common.model.response.form.FormVO;
import cn.axzo.workflow.common.model.response.form.definition.FormDefinitionVO;
import cn.axzo.workflow.common.model.response.form.instance.FormInstanceVO;
import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper;
import cn.axzo.workflow.core.engine.cmd.GetFormInstanceAndPermissionCmd;
import cn.axzo.workflow.core.repository.entity.ExtAxBpmnFormRelation;
import cn.axzo.workflow.core.service.BpmnProcessDefinitionService;
import cn.axzo.workflow.core.service.BpmnProcessTaskForEsService;
import cn.axzo.workflow.core.service.ExtAxBpmnFormRelationService;
import cn.axzo.workflow.core.service.FormCoreService;
import cn.axzo.workflow.form.service.converter.FormFieldConverter;
import cn.axzo.workflow.form.service.converter.FormInstanceConverter;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.common.engine.api.FlowableObjectNotFoundException;
import org.flowable.common.engine.impl.interceptor.CommandExecutor;
import org.flowable.engine.impl.cmd.GetBpmnModelCmd;
import org.flowable.form.api.FormInfo;
import org.flowable.form.api.FormInstanceInfo;
import org.flowable.form.api.FormRepositoryService;
import org.flowable.form.model.FormField;
import org.flowable.form.model.SimpleFormModel;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import static cn.axzo.workflow.common.code.FormModelRespCode.FORM_MODEL_NOT_EXISTS;
import static cn.axzo.workflow.common.constant.FormConstants.FIELD_PROPERTY_EDITABLE;
import static cn.axzo.workflow.common.constant.FormConstants.FIELD_PROPERTY_HIDDEN;
import static cn.axzo.workflow.common.constant.FormConstants.FIELD_PROPERTY_READONLY;
import static cn.axzo.workflow.common.constant.FormConstants.FIELD_PROPERTY_REQUIRED;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER;
/**
* @author wangli
* @since 2024-11-14 10:18
*/
@Slf4j
@Service
public class FormCoreServiceImpl implements FormCoreService {
@Resource
private SpringProcessEngineConfiguration springProcessEngineConfiguration;
@Resource
private ExtAxBpmnFormRelationService bpmnFormRelationService;
@Resource
private BpmnProcessTaskForEsService bpmnProcessTaskForEsService;
@Resource
private FormInstanceConverter formInstanceConverter;
@Resource
private FormRepositoryService formRepositoryService;
@Resource
private BpmnProcessDefinitionService bpmnProcessDefinitionService;
@Resource
private FormFieldConverter formFieldConverter;
@Override
public List<FormVO> pageForm(BpmnFormRelationSearchDTO searchDTO) {
List<ExtAxBpmnFormRelation> list = bpmnFormRelationService.keyQuery(searchDTO);
return list.stream()
.collect(Collectors.collectingAndThen(Collectors.toMap(ExtAxBpmnFormRelation::getKey, Function.identity(), (s, t) -> s), x -> new ArrayList<>(x.values())))
.stream()
.map(e -> FormVO.builder()
.key(e.getKey())
.tenantId(e.getTenantId())
.build())
.collect(Collectors.toList());
}
@Override
public FormDefinitionVO getStartForm(StartFormSearchDTO dto) {
FormInfo formModel;
try {
formModel = formRepositoryService.getFormModelByKey(dto.getKey(), dto.getTenantId(), true);
} catch (FlowableObjectNotFoundException e) {
log.warn("can't found form model");
throw new WorkflowEngineException(FORM_MODEL_NOT_EXISTS);
}
BpmnProcessDefinitionVO definitionVO = bpmnProcessDefinitionService.getActiveProcessDefinitionByKey(dto.getKey(), dto.getTenantId());
CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor();
BpmnModel bpmnModel = commandExecutor.execute(new GetBpmnModelCmd(definitionVO.getId()));
BpmnMetaParserHelper.getFormFieldPermissionConf(bpmnModel.getFlowElement(NODE_STARTER.getType())).ifPresent(permission -> {
Map<String, FormPermissionMetaInfo> permissionMap = permission.stream()
.collect(Collectors.toMap(FormPermissionMetaInfo::getFieldId, Function.identity(), (s, t) -> s));
if (formModel.getFormModel() instanceof SimpleFormModel) {
SimpleFormModel simpleFormModel = (SimpleFormModel) formModel.getFormModel();
simpleFormModel.listAllFields().forEach(field -> {
Map<String, Object> params = CollectionUtils.isEmpty(field.getParams()) ? new HashMap<>() : field.getParams();
FormPermissionMetaInfo meta = permissionMap.getOrDefault(field.getId(), new FormPermissionMetaInfo());
params.put(FIELD_PROPERTY_REQUIRED, meta.getRequired());
params.put(FIELD_PROPERTY_EDITABLE, meta.getEditable());
params.put(FIELD_PROPERTY_READONLY, meta.getReadonly());
params.put(FIELD_PROPERTY_HIDDEN, meta.getHidden());
field.setParams(params);
});
}
});
FormDefinitionVO formDefinitionVO = new FormDefinitionVO();
formDefinitionVO.setFormDefinitionId(formModel.getId());
formDefinitionVO.setName(formModel.getName());
formDefinitionVO.setKey(formModel.getKey());
formDefinitionVO.setVersion(formModel.getVersion());
formDefinitionVO.setTenantId(dto.getTenantId());
List<FormField> fields = ((SimpleFormModel) formModel.getFormModel()).getFields();
formDefinitionVO.setFields(formFieldConverter.toVos(fields));
return formDefinitionVO;
}
@Override
public FormInstanceVO getFormInstance(FormDetailDTO dto) {
CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor();
FormInstanceInfo formInstanceInfo = commandExecutor.execute(new GetFormInstanceAndPermissionCmd(bpmnFormRelationService,
bpmnProcessTaskForEsService, dto.getAssigner(), dto.getProcessInstanceId(), dto.getTaskId()));
return formInstanceConverter.toVo(formInstanceInfo);
}
}

View File

@ -10,4 +10,9 @@ create table ext_ax_bpmn_form_relation
create_at datetime default CURRENT_TIMESTAMP not null comment '创建时间',
create_by bigint default 0 not null comment '创建人',
is_delete bigint default 0 not null comment '是否删除:0否,1是'
);
);
-- 流程日志表增加字段权限配置
alter table ext_ax_process_log
add form_field_permission_conf json null comment '表单字段权限' after button_conf;

View File

@ -3,6 +3,7 @@ package cn.axzo.workflow.form.service;
import cn.axzo.workflow.common.model.request.form.instance.FormContentUpdateDTO;
import cn.axzo.workflow.common.model.request.form.instance.FormInstanceSearchDTO;
import cn.axzo.workflow.common.model.response.form.instance.FormInstanceVO;
import org.flowable.form.api.FormInstanceInfo;
/**
@ -25,5 +26,5 @@ public interface FormInstanceService {
* @param dto
* @return
*/
FormInstanceInfo getFormInstanceInfo(FormInstanceSearchDTO dto);
FormInstanceVO getFormInstanceInfo(FormInstanceSearchDTO dto);
}

View File

@ -0,0 +1,107 @@
package cn.axzo.workflow.form.service.converter;
import cn.axzo.workflow.common.model.request.form.definition.FormFieldDTO;
import cn.axzo.workflow.common.model.response.form.instance.FormInstanceVO;
import cn.axzo.workflow.common.model.response.form.model.FormModelVO;
import org.flowable.form.api.FormInstanceInfo;
import org.flowable.form.api.FormModel;
import org.flowable.form.model.FormContainer;
import org.flowable.form.model.FormField;
import org.flowable.form.model.SimpleFormModel;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import static org.mapstruct.NullValueCheckStrategy.ALWAYS;
/**
* 表单实例模型的 MapStruts 转换器
*
* @author wangli
* @since 2024-11-14 10:31
*/
@Mapper(
componentModel = "spring",
nullValueCheckStrategy = ALWAYS,
imports = Arrays.class
)
public interface FormInstanceConverter extends EntityConverter<FormInstanceVO, FormInstanceInfo> {
@Mapping(target = "id", source = "entity.id")
@Mapping(target = "name", source = "entity.name")
@Mapping(target = "key", source = "entity.key")
@Mapping(target = "version", source = "entity.version")
@Mapping(target = "formModel", expression = "java(formModelConverter(entity.getFormModel()))")
@Mapping(target = "formInstanceId", source = "entity.formInstanceId")
@Mapping(target = "submittedBy", source = "entity.submittedBy")
@Mapping(target = "submittedDate", source = "entity.submittedDate")
@Mapping(target = "taskId", source = "entity.taskId")
@Mapping(target = "processInstanceId", source = "entity.processInstanceId")
@Mapping(target = "processDefinitionId", source = "entity.processDefinitionId")
@Mapping(target = "tenantId", source = "entity.tenantId")
FormInstanceVO toVo(FormInstanceInfo entity);
default FormModelVO formModelConverter(FormModel formModel) {
FormModelVO formModelVO = new FormModelVO();
if (formModel instanceof SimpleFormModel) {
SimpleFormModel simpleFormModel = (SimpleFormModel) formModel;
formModelVO.setName(simpleFormModel.getName());
formModelVO.setKey(simpleFormModel.getKey());
formModelVO.setVersion(simpleFormModel.getVersion());
formModelVO.setFields(formContainersToFormFieldDTOs(simpleFormModel.getFields()));
}
return formModelVO;
}
default FormFieldDTO formFieldConverter(FormField formField) {
FormFieldDTO formFieldDTO = new FormFieldDTO();
formFieldDTO.setType(formField.getType());
formFieldDTO.setId(formField.getId());
formFieldDTO.setName(formField.getName());
formFieldDTO.setValue(castList(formField.getValue()));
formFieldDTO.setPlaceholder(formField.getPlaceholder());
formFieldDTO.setParams(formField.getParams());
if (formField instanceof FormContainer) {
FormContainer formContainer = (FormContainer) formField;
formFieldDTO.setFields(formContainerListsToFormFieldDTOLists(formContainer.getFields()));
}
return formFieldDTO;
}
default List<String> castList(Object value) {
if (Objects.isNull(value)) {
return (List<String>) value;
}
List<String> values = new ArrayList<>();
if (value instanceof String) {
values.add((String) value);
}
return values;
}
// 辅助方法用于处理List<FormContainer>到List<FormFieldDTO>的转换调用上面自定义的转换方法
default List<FormFieldDTO> formContainersToFormFieldDTOs(List<FormField> formContainers) {
if (formContainers == null) {
return null;
}
return formContainers.stream()
.map(e -> formFieldConverter(e))
.collect(Collectors.toList());
}
// 辅助方法用于处理List<List<FormContainer>>到List<List<FormFieldDTO>>的转换调用上面的辅助方法
default List<List<FormFieldDTO>> formContainerListsToFormFieldDTOLists(List<List<FormField>> formContainerLists) {
if (formContainerLists == null) {
return null;
}
return formContainerLists.stream()
.map(this::formContainersToFormFieldDTOs)
.collect(Collectors.toList());
}
}

View File

@ -4,12 +4,14 @@ import cn.axzo.workflow.common.code.BpmnTaskRespCode;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.request.form.instance.FormContentUpdateDTO;
import cn.axzo.workflow.common.model.request.form.instance.FormInstanceSearchDTO;
import cn.axzo.workflow.common.model.response.form.instance.FormInstanceVO;
import cn.axzo.workflow.form.service.FormInstanceService;
import cn.axzo.workflow.form.service.converter.FormInstanceConverter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.HistoryService;
import org.flowable.engine.TaskService;
import org.flowable.form.api.FormInfo;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.form.api.FormInstanceInfo;
import org.flowable.form.api.FormRepositoryService;
import org.flowable.form.api.FormService;
@ -20,6 +22,8 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Objects;
import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_ID_NOT_EXISTS;
/**
* 表单实例 Service 实现
@ -32,14 +36,16 @@ import java.util.Objects;
@RequiredArgsConstructor
public class FormInstanceServiceImpl implements FormInstanceService {
@Resource
private FormRepositoryService formRepositoryService;
@Resource
private FormService formService;
@Resource
private TaskService taskService;
@Resource
private FormInstanceConverter formInstanceConverter;
@Resource
private HistoryService historyService;
@Resource
private FormRepositoryService formRepositoryService;
@Override
public void updateFormContent(FormContentUpdateDTO dto) {
@ -55,7 +61,6 @@ public class FormInstanceServiceImpl implements FormInstanceService {
throw new WorkflowEngineException(BpmnTaskRespCode.TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF);
}
FormInfo formInfo = formRepositoryService.getFormModelById(dto.getFormDefinitionId());
if (Objects.nonNull(task.getProcessInstanceId())) {
formService.saveFormInstanceByFormDefinitionId(dto.getFormVariables(), dto.getFormDefinitionId(),
dto.getTaskId(),
@ -68,8 +73,15 @@ public class FormInstanceServiceImpl implements FormInstanceService {
}
@Override
public FormInstanceInfo getFormInstanceInfo(FormInstanceSearchDTO dto) {
public FormInstanceVO getFormInstanceInfo(FormInstanceSearchDTO dto) {
HistoricProcessInstance instance = historyService.createHistoricProcessInstanceQuery().processInstanceId(dto.getProcessInstanceId()).singleResult();
if (Objects.isNull(instance)) {
throw new WorkflowEngineException(PROCESS_INSTANCE_ID_NOT_EXISTS, dto.getProcessInstanceId());
}
return formService.getFormInstanceModelById(dto.getFormDefinitionId(), dto.getTaskId(), dto.getProcessInstanceId(), dto.getVariables(), dto.getTenantId(), true);
String processDefinitionKey = instance.getProcessDefinitionKey();
FormInstanceInfo formInstanceInfo = formService.getFormInstanceModelByKey(processDefinitionKey, dto.getTaskId(), dto.getProcessInstanceId(), dto.getVariables(), instance.getTenantId(), true);
return formInstanceConverter.toVo(formInstanceInfo);
}
}

View File

@ -29,10 +29,15 @@ import org.flowable.form.api.FormInfo;
import org.flowable.form.api.FormInstanceInfo;
import org.flowable.form.api.FormRepositoryService;
import org.flowable.form.api.FormService;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.PropertyPlaceholderHelper;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@ -45,7 +50,9 @@ import javax.annotation.Resource;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@ -86,10 +93,12 @@ public class TestController {
private FormDefinitionService formDefinitionService;
@Resource
private ExtAxBpmnFormRelationService bpmnFormRelationService;
// @Autowired
// @Autowired
// private WorkflowCoreService workflowCoreService;
// @Autowired
// private WorkflowManageService workflowManageService;
@Resource
private SpringProcessEngineConfiguration processEngineConfiguration;
@RepeatSubmit
@GetMapping("/test")
@ -319,4 +328,30 @@ public class TestController {
FormInstanceInfo formInstance = formService.getFormInstanceModelById(formDefinition.getId(), dto.getTaskId(), dto.getProcessInstanceId(), dto.getVariables(), relation.getTenantId(), false);
return CommonResponse.success(formInstance);
}
/**
* 表达式测试
*
* @param expression
* @param variables
* @return
*/
@PostMapping("/expression/testing")
public CommonResponse<String> parseExpression(@RequestParam String expression, @RequestBody Map<String, String> variables) {
// ExpressionManager expressionManager = processEngineConfiguration.getExpressionManager();
// Expression exp = expressionManager.createExpression(expression);
// VariableContainerWrapper variableContainer = new VariableContainerWrapper(variables);
// String stringFromField = ExpressionUtils.getStringFromField(exp, variableContainer);
// Object value = exp.getValue(variableContainer);
PropertyPlaceholderHelper propertyPlaceholderHelper = new PropertyPlaceholderHelper("${", "}");
Properties properties = new Properties();
variables.forEach(properties::setProperty);
// 自定义PlaceholderResolver实现类用于处理占位符找不到对应值时返回空字符串
PropertyPlaceholderHelper.PlaceholderResolver resolver = placeholder -> properties.getProperty(placeholder, "");
String value = propertyPlaceholderHelper.replacePlaceholders(expression, resolver);
return CommonResponse.success(value);
}
}

View File

@ -112,7 +112,10 @@ public class BpmnProcessTaskController extends BasicPopulateAvatarController imp
return success(true);
}
@PostMapping("/approveWithForm")
@Operation(summary = "通过任务")
@Override
@PostMapping("/form/approve")
@RepeatSubmit
public CommonResponse<Boolean> approveTaskWithForm(@Validated @RequestBody BpmnTaskAuditWithFormDTO dto) {
log.info("同意 approveTask===>>>参数:{}", JSON.toJSONString(dto));
List<AttachmentDTO> tempAttachments = ListUtils.defaultIfNull(dto.getAttachmentList(), new ArrayList<>());

View File

@ -3,14 +3,18 @@ package cn.axzo.workflow.server.controller.web.form;
import cn.axzo.workflow.client.feign.manage.FormInstanceApi;
import cn.axzo.workflow.common.model.dto.BpmnFormRelationSearchDTO;
import cn.axzo.workflow.common.model.request.form.definition.FormDefinitionSearchDTO;
import cn.axzo.workflow.common.model.request.form.definition.StartFormSearchDTO;
import cn.axzo.workflow.common.model.request.form.instance.FormContentUpdateDTO;
import cn.axzo.workflow.common.model.request.form.instance.FormDetailDTO;
import cn.axzo.workflow.common.model.request.form.instance.FormInstanceSearchDTO;
import cn.axzo.workflow.common.model.request.form.instance.FormSearchDTO;
import cn.axzo.workflow.common.model.response.BpmPageResult;
import cn.axzo.workflow.common.model.response.form.FormVO;
import cn.axzo.workflow.common.model.response.form.definition.FormDefinitionVO;
import cn.axzo.workflow.common.model.response.form.instance.FormInstanceVO;
import cn.axzo.workflow.core.repository.entity.ExtAxBpmnFormRelation;
import cn.axzo.workflow.core.service.ExtAxBpmnFormRelationService;
import cn.axzo.workflow.core.service.FormCoreService;
import cn.axzo.workflow.form.service.FormDefinitionService;
import cn.axzo.workflow.form.service.FormInstanceService;
import cn.axzo.workflow.server.common.annotation.ErrorReporter;
@ -33,7 +37,7 @@ import java.util.stream.Collectors;
import static cn.azxo.framework.common.model.CommonResponse.success;
/**
* 表单模型相关控制器
* 表单实例相关控制器
*
* @author wangli
* @since 2023/7/21 15:23
@ -46,11 +50,7 @@ import static cn.azxo.framework.common.model.CommonResponse.success;
public class FormInstanceController implements FormInstanceApi {
@Resource
private FormInstanceService formInstanceService;
@Resource
private FormDefinitionService formDefinitionService;
@Resource
private ExtAxBpmnFormRelationService bpmnFormRelationService;
private FormCoreService formCoreService;
@Operation(summary = "表单列表")
@PostMapping("/form/page")
@ -58,29 +58,18 @@ public class FormInstanceController implements FormInstanceApi {
BpmnFormRelationSearchDTO searchDTO = new BpmnFormRelationSearchDTO();
searchDTO.setKey(dto.getKey());
searchDTO.setTenantId(dto.getTenantId());
List<ExtAxBpmnFormRelation> list = bpmnFormRelationService.keyQuery(searchDTO);
List<FormVO> result = list
.stream()
.collect(Collectors.collectingAndThen(Collectors.toMap(ExtAxBpmnFormRelation::getKey,Function.identity(),(s,t)->s), x->new ArrayList<>(x.values())))
.stream()
.map(e -> FormVO.builder()
.key(e.getKey())
.tenantId(e.getTenantId())
.build())
.collect(Collectors.toList());
return success(result);
return success(formCoreService.pageForm(searchDTO));
}
@Operation(summary = "获取指定业务含有表单的定义内容")
@PostMapping("/init")
public CommonResponse<FormDefinitionVO> getFormDefinition(@Validated @RequestBody FormDefinitionSearchDTO dto) {
return success(formDefinitionService.get(dto));
@Operation(summary = "获取指定业务含有表单的定义内容和权限")
@PostMapping("/start/form")
public CommonResponse<FormDefinitionVO> getFormDefinition(@Validated @RequestBody StartFormSearchDTO dto) {
return success(formCoreService.getStartForm(dto));
}
@Operation(summary = "获取指定表单审批的实例信息")
@PostMapping("/render")
public CommonResponse<FormInstanceInfo> getFormInstance(@Validated @RequestBody FormInstanceSearchDTO dto) {
return success(formInstanceService.getFormInstanceInfo(dto));
public CommonResponse<FormInstanceVO> getFormInstance(@Validated @RequestBody FormDetailDTO dto) {
return success(formCoreService.getFormInstance(dto));
}
}

View File

@ -60,9 +60,11 @@ import cn.axzo.workflow.common.model.response.category.CategoryItemVO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import cn.axzo.workflow.common.model.request.form.definition.FormDefinitionSearchDTO;
import cn.axzo.workflow.common.model.request.form.instance.FormInstanceSearchDTO;
import cn.axzo.workflow.common.model.request.form.instance.FormSearchDTO;
import cn.axzo.workflow.common.model.response.form.FormVO;
import cn.axzo.workflow.common.model.response.form.definition.FormDefinitionVO;
import cn.axzo.workflow.common.model.response.form.instance.FormInstanceVO;
import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonMetaInfo;
import cn.axzo.workflow.common.model.request.bpmn.model.BpmnModelCreateDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.BpmnModelSearchDTO;
@ -463,6 +465,10 @@ public interface WorkflowManageService {
@InvokeMode(SYNC)
FormDefinitionVO getFormDefinition(@Validated @RequestBody FormDefinitionSearchDTO dto);
@PostMapping("/api/form/instance/get")
@InvokeMode(SYNC)
FormInstanceVO getFormInstance(@Validated @RequestBody FormInstanceSearchDTO dto);
/**
* 获取流程操作按钮列表
*