feat(REQ-3340) - 处理表单多种业务组件的保存和回显逻辑

This commit is contained in:
wangli 2025-01-22 18:36:18 +08:00
parent 4d39265ecd
commit 568bb07e31
16 changed files with 649 additions and 80 deletions

View File

@ -18,6 +18,10 @@ public enum FormInstanceRespCode implements IModuleRespCode {
FORM_FIELD_VALIDATOR_ERROR("003", "表单字段校验不通过"),
FORM_INSTANCE_PARSE_ERROR("004", "表单实例数据解析错误"),
FORM_INSTANCE_DATA_NOT_FOUND("005", "未找到指定实例【{}】的表单数据"),
FORM_DATA_PARSE_ERROR("006", "表单数据解析异常"),
FORM_DATA_PARSE_ERROR_BY_UPLOAD("007", "表单上传组件的数据解析异常"),
FORM_DATA_PARSE_ERROR_BY_IMAGE("008", "表单图片组件的数据解析异常"),
;
private final String code;

View File

@ -17,4 +17,12 @@ public interface FormConstants {
String FIELD_PROPERTY_HIDDEN = "hidden";
String FIELD_PROPERTY_DEFAULT_VALUE= "defaultValue";
String FORM_FIELD_TYPE_TEXTAREA = "textarea";
String FORM_FIELD_TYPE_IMAGE = "image";
String FORM_FIELD_TYPE_CUSTOM_COMPONENT = "customComponent";
String FORM_FIELD_TYPE_TASK_ORDER = "taskOrder";
String FORM_FIELD_TYPE_RECTIFY_ORDER = "rectifyOrder";
String FORM_FIELD_TYPE_CHANGE_SIGNATURE_ORDER = "changeSignatureOrder";
}

View File

@ -1,6 +1,5 @@
package cn.axzo.workflow.common.model.dto;
import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@ -14,7 +13,6 @@ import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Deprecated // 这个类型不应该产生建议后面迭代替换为 AttachmentDTO
public class UploadFieldDTO {
/**
@ -25,24 +23,11 @@ public class UploadFieldDTO {
/**
* 文件类型
*/
private String fileType;
private String fileUrl;
/**
* 文件 oss key
*/
private String fileKey;
public String toString() {
return JSON.toJSONString(this);
}
public String toSpecString() {
return this.toString().replaceAll(",", "|");
}
public static UploadFieldDTO toObject(String str) {
str = str.trim().replaceAll("\\|",",");
return JSON.parseObject(str, UploadFieldDTO.class);
}
}

View File

@ -3,10 +3,7 @@ package cn.axzo.workflow.common.model.request.bpmn.process;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
import java.util.HashMap;
import java.util.Map;
/**
@ -19,8 +16,11 @@ import java.util.Map;
@Data
public class BpmnProcessInstanceCreateWithFormDTO {
/**
* todo 需要补充各种组件传入的模型文档
*/
@ApiModelProperty(value = "通过表单创建流程时传入的初始表单数据")
private Map<String, Object> startFormVariables = new HashMap<>();
private Map<String, Object> startFormVariables;
/**
* 工作流实例集成表单后,可以通过表单 key 组装成的变量存入该变量的值,可用于后续流程的流转
* <p>

View File

@ -65,4 +65,10 @@ public class AttachmentDTO implements Serializable {
@IndexField(fieldType = FieldType.KEYWORD)
private String url;
/**
* 文件 OSS fileKey
*/
@ApiModelProperty(value = "附件 oss 的 fileKey")
private String fileKey;
}

View File

@ -51,7 +51,7 @@ public class FormFieldDTO {
* 部分组件是多值比如日期区间多选, 多附件
*/
@ApiModelProperty(value = "表单字段默认值", example = "account")
private List<String> value;
private Object value;
/**
* 表单占位提示

View File

@ -3,6 +3,7 @@ package cn.axzo.workflow.core.conf;
import cn.axzo.workflow.core.common.utils.SpringContextUtils;
import cn.axzo.workflow.core.engine.behavior.CustomActivityBehaviorFactory;
import cn.axzo.workflow.core.engine.cmd.CustomCommandContextFactory;
import cn.axzo.workflow.core.engine.formhandler.CustomFormFieldHandler;
import cn.axzo.workflow.core.engine.id.BasedNacosSnowflakeIdGenerator;
import cn.axzo.workflow.core.engine.interceptor.CustomRetryInterceptor;
import cn.axzo.workflow.core.engine.job.AsyncAbortProcessInstanceJobHandler;
@ -36,7 +37,6 @@ import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.flowable.common.engine.api.delegate.event.FlowableEventListener;
import org.flowable.common.engine.impl.history.HistoryLevel;
import org.flowable.form.engine.configurator.FormEngineConfigurator;
import org.flowable.job.service.JobProcessor;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
@ -132,6 +132,9 @@ public class FlowableConfiguration {
configuration.setCustomPreCommandInterceptors(Lists.newArrayList(
new CustomRetryInterceptor()
));
// form configuration
configuration.setFormFieldValidationEnabled(true);
configuration.setFormFieldHandler(new CustomFormFieldHandler());
};
}

View File

@ -2,7 +2,6 @@ package cn.axzo.workflow.core.engine.cmd;
import cn.axzo.workflow.common.enums.BpmnFlowNodeType;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.dto.UploadFieldDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditWithFormDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
@ -13,7 +12,6 @@ import cn.axzo.workflow.core.repository.entity.ExtAxProcessLog;
import cn.axzo.workflow.core.service.ExtAxBpmnFormRelationService;
import cn.axzo.workflow.core.service.ExtAxProcessLogService;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.RuntimeService;
@ -33,7 +31,6 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -211,19 +208,19 @@ public class CustomApproveTaskWithFormCmd extends AbstractCommand<Void> implemen
.deploymentId(relation.getFormDeploymentId())
.singleResult();
Authentication.setAuthenticatedUserId(approver.buildAssigneeId());
formVariables.entrySet().forEach(e -> {
if (e.getValue() instanceof Collection) {
List<String> convertUploads = ((Collection<?>) e.getValue()).stream().map(i -> {
if (i instanceof String) {
return UploadFieldDTO.toObject(String.valueOf(i)).toSpecString();
} else {
ObjectMapper objectMapper = SpringContextUtils.getBean(ObjectMapper.class);
return objectMapper.convertValue(i, UploadFieldDTO.class).toSpecString();
}
}).collect(Collectors.toList());
e.setValue(StringUtils.collectionToCommaDelimitedString(convertUploads));
}
});
// formVariables.entrySet().forEach(e -> {
// if (e.getValue() instanceof Collection) {
// List<String> convertUploads = ((Collection<?>) e.getValue()).stream().map(i -> {
// if (i instanceof String) {
// return UploadFieldDTO.toObject(String.valueOf(i)).toSpecString();
// } else {
// ObjectMapper objectMapper = SpringContextUtils.getBean(ObjectMapper.class);
// return objectMapper.convertValue(i, UploadFieldDTO.class).toSpecString();
// }
// }).collect(Collectors.toList());
// e.setValue(StringUtils.collectionToCommaDelimitedString(convertUploads));
// }
// });
taskService.completeTaskWithForm(taskId, formDefinition.getId(), null, formVariables);
Authentication.setAuthenticatedUserId(null);
} else {

View File

@ -1,19 +1,44 @@
package cn.axzo.workflow.core.engine.cmd;
import cn.axzo.workflow.common.model.dto.UploadFieldDTO;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.api.delegate.Expression;
import org.flowable.common.engine.impl.el.VariableContainerWrapper;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.form.api.FormInstance;
import org.flowable.form.api.FormInstanceInfo;
import org.flowable.form.api.FormInstanceQuery;
import org.flowable.form.engine.FormEngineConfiguration;
import org.flowable.form.engine.impl.cmd.GetFormInstanceModelCmd;
import org.flowable.form.engine.impl.util.CommandContextUtil;
import org.flowable.form.model.ExpressionFormField;
import org.flowable.form.model.FormField;
import org.flowable.form.model.FormFieldTypes;
import org.flowable.form.model.Option;
import org.flowable.form.model.OptionFormField;
import org.flowable.form.model.SimpleFormModel;
import org.joda.time.LocalDate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static cn.axzo.workflow.common.constant.FormConstants.FORM_FIELD_TYPE_CHANGE_SIGNATURE_ORDER;
import static cn.axzo.workflow.common.constant.FormConstants.FORM_FIELD_TYPE_CUSTOM_COMPONENT;
import static cn.axzo.workflow.common.constant.FormConstants.FORM_FIELD_TYPE_IMAGE;
import static cn.axzo.workflow.common.constant.FormConstants.FORM_FIELD_TYPE_RECTIFY_ORDER;
import static cn.axzo.workflow.common.constant.FormConstants.FORM_FIELD_TYPE_TASK_ORDER;
/**
* 自定义的获取表单模型和最新表单内容的命令实现
*
@ -78,4 +103,208 @@ public class CustomGetFormInstanceModelCmd extends GetFormInstanceModelCmd {
log.info("未查询到流程实例关联的表单实例数据");
return null;
}
@Override
public void fillVariablesWithFormValues(Map<String, JsonNode> submittedFormFieldMap, List<FormField> allFields) {
for (FormField field : allFields) {
JsonNode fieldValueNode = submittedFormFieldMap.get(field.getId());
if (fieldValueNode == null || fieldValueNode.isNull()) {
continue;
}
String fieldType = field.getType();
String fieldValue = fieldValueNode.asText();
if (FormFieldTypes.DATE.equals(fieldType)) {
try {
if (org.apache.commons.lang3.StringUtils.isNotEmpty(fieldValue)) {
LocalDate dateValue = LocalDate.parse(fieldValue);
variables.put(field.getId(), dateValue.toString("d-M-yyyy"));
}
} catch (Exception e) {
log.error("Error parsing form date value for process instance {} and task {} with value {}", processInstanceId, taskId, fieldValue, e);
}
// } else if (FormFieldTypes.UPLOAD.equals(fieldType) || FormConstants.FORM_FIELD_TYPE_IMAGE.equals(fieldType)) {
// FormEngineConfiguration formEngineConfiguration = CommandContextUtil.getFormEngineConfiguration();
// ObjectMapper objectMapper = formEngineConfiguration.getObjectMapper();
// try {
// List<UploadFieldDTO> uploadFiles = objectMapper.readValue(fieldValue, new TypeReference<List<UploadFieldDTO>>() {
// });
// variables.put(field.getId(), uploadFiles);
// } catch (JsonProcessingException e) {
// throw new WorkflowEngineException(FORM_DATA_PARSE_ERROR_BY_UPLOAD);
// }
} else if (fieldValueNode.isBoolean()) {
variables.put(field.getId(), fieldValueNode.asBoolean());
} else if (fieldValueNode.isLong()) {
variables.put(field.getId(), fieldValueNode.asLong());
} else if (fieldValueNode.isDouble()) {
variables.put(field.getId(), fieldValueNode.asDouble());
} else {
variables.put(field.getId(), fieldValue);
}
}
}
@Override
protected void fillFormInstanceValues(FormInstanceInfo formInstanceModel, FormInstance formInstance, Map<String, JsonNode> formInstanceFieldMap, ObjectMapper objectMapper) {
try {
JsonNode submittedNode = objectMapper.readTree(formInstance.getFormValueBytes());
if (submittedNode == null) {
return;
}
if (submittedNode.get("values") != null) {
JsonNode valuesNode = submittedNode.get("values");
Iterator<String> fieldIdIterator = valuesNode.fieldNames();
while (fieldIdIterator.hasNext()) {
String fieldId = fieldIdIterator.next();
JsonNode valueNode = valuesNode.get(fieldId);
formInstanceFieldMap.put(fieldId, valueNode);
}
}
if (submittedNode.get("flowable_form_outcome") != null) {
JsonNode outcomeNode = submittedNode.get("flowable_form_outcome");
if (!outcomeNode.isNull() && org.apache.commons.lang3.StringUtils.isNotEmpty(outcomeNode.asText())) {
formInstanceModel.setSelectedOutcome(outcomeNode.asText());
}
}
} catch (Exception e) {
throw new FlowableException("Error parsing form instance " + formInstance.getId(), e);
}
}
@Override
protected void fillFormFieldValues(FormInstance formInstance, FormInstanceInfo formInstanceModel, CommandContext commandContext) {
FormEngineConfiguration formEngineConfiguration = CommandContextUtil.getFormEngineConfiguration();
SimpleFormModel formModel = (SimpleFormModel) formInstanceModel.getFormModel();
List<FormField> allFields = formModel.listAllFields();
if (allFields != null) {
Map<String, JsonNode> formInstanceFieldMap = new HashMap<>();
if (formInstance != null) {
fillFormInstanceValues(formInstanceModel, formInstance, formInstanceFieldMap, formEngineConfiguration.getObjectMapper());
fillVariablesWithFormValues(formInstanceFieldMap, allFields);
}
for (FormField field : allFields) {
if (field instanceof OptionFormField) {
OptionFormField optionFormField = (OptionFormField) field;
if (optionFormField.getOptionsExpression() != null) {
// Drop down options to be populated from an expression
Expression optionsExpression = formEngineConfiguration.getExpressionManager().createExpression(optionFormField.getOptionsExpression());
Object value = null;
try {
value = optionsExpression.getValue(new VariableContainerWrapper(variables));
} catch (Exception e) {
throw new FlowableException("Error getting value for optionsExpression: " + optionFormField.getOptionsExpression(), e);
}
if (value instanceof List) {
@SuppressWarnings("unchecked")
List<Option> options = (List<Option>) value;
optionFormField.setOptions(options);
} else if (value instanceof String) {
String json = (String) value;
try {
List<Option> options = formEngineConfiguration.getObjectMapper().readValue(json, new TypeReference<List<Option>>() {
});
optionFormField.setOptions(options);
} catch (Exception e) {
throw new FlowableException("Error parsing optionsExpression json value: " + json, e);
}
} else {
throw new FlowableException("Invalid type from evaluated expression for optionsExpression: " + optionFormField.getOptionsExpression() + ", resulting type:" + value.getClass().getName());
}
}
Object variableValue = variables.get(field.getId());
optionFormField.setValue(variableValue);
} else if (FormFieldTypes.HYPERLINK.equals(field.getType())) {
Object variableValue = variables.get(field.getId());
// process expression if there is no value, otherwise keep it
if (variableValue != null) {
field.setValue(variableValue);
} else {
// No value set, process as expression
if (field.getParam("hyperlinkUrl") != null) {
String hyperlinkUrl = field.getParam("hyperlinkUrl").toString();
Expression formExpression = formEngineConfiguration.getExpressionManager().createExpression(hyperlinkUrl);
try {
field.setValue(formExpression.getValue(new VariableContainerWrapper(variables)));
} catch (Exception e) {
log.error("Error getting value for hyperlink expression {} {}", hyperlinkUrl, e.getMessage(), e);
}
}
}
} else if (field instanceof ExpressionFormField) {
ExpressionFormField expressionField = (ExpressionFormField) field;
Expression formExpression = formEngineConfiguration.getExpressionManager().createExpression(expressionField.getExpression());
try {
field.setValue(formExpression.getValue(new VariableContainerWrapper(variables)));
} catch (Exception e) {
log.error("Error getting value for expression {} {}", expressionField.getExpression(), e.getMessage());
}
} else if (FormFieldTypes.UPLOAD.equals(field.getType())
|| FORM_FIELD_TYPE_IMAGE.equals(field.getType())) {
// Multiple docs are stored as comma-separated string ids,
// explicitly storing them as an array so they're serialized properly
if (variables.containsKey(field.getId())) {
String uploadValue = (String) variables.get(field.getId());
if (uploadValue != null) {
try {
List<UploadFieldDTO> uploadFiles = formEngineConfiguration.getObjectMapper()
.readValue(uploadValue, new TypeReference<List<UploadFieldDTO>>() {
});
field.setValue(uploadFiles);
} catch (JsonProcessingException e) {
throw new FlowableException("Error parsing upload files json value: " + uploadValue, e);
}
}
}
} else if (FORM_FIELD_TYPE_TASK_ORDER.equals(field.getType())
|| FORM_FIELD_TYPE_RECTIFY_ORDER.equals(field.getType())
|| FORM_FIELD_TYPE_CHANGE_SIGNATURE_ORDER.equals(field.getType())) {
if (variables.containsKey(field.getId())) {
String listValue = (String) variables.get(field.getId());
if (listValue != null) {
try {
List<String> ids = formEngineConfiguration.getObjectMapper()
.readValue(listValue, new TypeReference<List<String>>() {
});
field.setValue(ids);
} catch (JsonProcessingException e) {
throw new FlowableException("Error parsing business component ids json value: " + listValue, e);
}
}
}
} else if (FORM_FIELD_TYPE_CUSTOM_COMPONENT.equals(field.getType())) {
} else {
Object variableValue = variables.get(field.getId());
if (variableValue != null) {
if (variableValue instanceof LocalDate) {
field.setValue(((LocalDate) variableValue).toString("d-M-yyyy"));
} else if (variableValue instanceof Date) {
field.setValue(DATE_FORMAT.format(((Date) variableValue).toInstant()));
} else {
field.setValue(variableValue);
}
}
}
field.setReadOnly(true);
}
}
}
}

View File

@ -0,0 +1,36 @@
package cn.axzo.workflow.core.engine.formhandler;
import org.flowable.content.api.ContentService;
import org.flowable.engine.impl.formhandler.DefaultFormFieldHandler;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.form.api.FormFieldHandler;
import org.flowable.form.api.FormInfo;
import java.util.Map;
/**
* 自定义的具体的表单组件类型处理器
*
* @author wangli
* @since 2025-01-22 13:58
*/
public class CustomFormFieldHandler extends DefaultFormFieldHandler implements FormFieldHandler {
@Override
public void handleFormFieldsOnSubmit(FormInfo formInfo,
String taskId,
String processInstanceId,
String scopeId,
String scopeType,
Map<String, Object> variables,
String tenantId) {
ContentService contentService = CommandContextUtil.getContentService();
if (contentService == null || formInfo == null) {
return;
}
}
@Override
public void enrichFormFields(FormInfo formInfo) {
super.enrichFormFields(formInfo);
}
}

View File

@ -8,7 +8,6 @@ import cn.axzo.workflow.common.enums.BpmnFlowNodeType;
import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum;
import cn.axzo.workflow.common.enums.WorkspaceType;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.dto.UploadFieldDTO;
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;
@ -115,7 +114,6 @@ import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
@ -422,25 +420,29 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic
ExtAxBpmnFormRelation relation = bpmnFormRelationService.queryByBpmnDefinitionId(definition.getId());
if (Objects.isNull(relation)) {
// 如果模型没有绑定表单则强制情况表单相关属性避免报错
// 如果模型没有绑定表单则强制清空表单相关属性避免异常
instanceBuilder.outcome(null);
} else {
// TODO 可以使用 cn.axzo.workflow.core.service.impl.BpmnProcessDefinitionServiceImpl.getProcessDefinition 接口拉取模型后进行表单数据转型字段验证的扩展实现
// 关于表单目前只有两类组件由于研发资源限制暂不做扩展性设计
dto.getStartFormVariables().entrySet().forEach(e -> {
if (e.getValue() instanceof Collection) {
List<String> convertUploads = ((Collection<?>) e.getValue()).stream().map(i -> {
if (i instanceof String) {
return UploadFieldDTO.toObject(String.valueOf(i)).toSpecString();
} else {
return objectMapper.convertValue(i, UploadFieldDTO.class).toSpecString();
}
}).collect(Collectors.toList());
e.setValue(StringUtils.collectionToCommaDelimitedString(convertUploads));
}
});
instanceBuilder.startFormVariables(dto.getStartFormVariables())
.outcome(dto.getOutcome());
// // TODO 可以使用 cn.axzo.workflow.core.service.impl.BpmnProcessDefinitionServiceImpl.getProcessDefinition 接口拉取模型后进行表单数据转型字段验证的扩展实现
// // 关于表单目前只有两类组件由于研发资源限制暂不做扩展性设计
// dto.getStartFormVariables().entrySet().forEach(e -> {
// if (e.getValue() instanceof Collection) {
// List<String> convertUploads = ((Collection<?>) e.getValue()).stream().map(i -> {
// if (i instanceof String) {
// return UploadFieldDTO.toObject(String.valueOf(i)).toSpecString();
// } else {
// return objectMapper.convertValue(i, UploadFieldDTO.class).toSpecString();
// }
// }).collect(Collectors.toList());
// e.setValue(StringUtils.collectionToCommaDelimitedString(convertUploads));
// }
// });
if (!CollectionUtils.isEmpty(dto.getStartFormVariables())) {
instanceBuilder.startFormVariables(dto.getStartFormVariables());
}
if (StringUtils.hasText(dto.getOutcome())) {
instanceBuilder.outcome(dto.getOutcome());
}
}
dto.getVariables().put(CREATE_INSTANCE_PARAMS, JSONUtil.toJsonStr(dto));
instanceBuilder.variables(dto.getVariables());

View File

@ -1,5 +1,6 @@
package cn.axzo.workflow.form.conf;
import cn.axzo.workflow.form.engine.api.CustomFormServiceImpl;
import org.flowable.form.spring.SpringFormEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
import org.springframework.context.annotation.Bean;
@ -18,6 +19,9 @@ public class FormFlowableConfiguration {
@Bean
public EngineConfigurationConfigurer<SpringFormEngineConfiguration> formEngineConfigurer() {
return configuration -> configuration.setDatabaseSchemaUpdate(DB_SCHEMA_UPDATE_TRUE);
return configuration -> {
configuration.setDatabaseSchemaUpdate(DB_SCHEMA_UPDATE_TRUE);
configuration.setFormService(new CustomFormServiceImpl(configuration));
};
}
}

View File

@ -0,0 +1,43 @@
package cn.axzo.workflow.form.engine.api;
import cn.axzo.workflow.form.engine.cmd.CustomCreateFormInstanceCmd;
import cn.axzo.workflow.form.engine.cmd.CustomGetVariablesFromFormSubmissionCmd;
import org.flowable.form.api.FormInfo;
import org.flowable.form.api.FormInstance;
import org.flowable.form.engine.FormEngineConfiguration;
import org.flowable.form.engine.impl.FormServiceImpl;
import java.util.Map;
/**
* 自定义扩展表单引擎中的 FormService
*
* @author wangli
* @since 2025-01-22 11:19
*/
public class CustomFormServiceImpl extends FormServiceImpl {
public CustomFormServiceImpl(FormEngineConfiguration engineConfiguration) {
super(engineConfiguration);
}
@Override
public void validateFormFields(FormInfo formInfo, Map<String, Object> values) {
// StartEvent Form Fields Validation
super.validateFormFields(formInfo, values);
}
@Override
public FormInstance createFormInstance(Map<String, Object> variables, FormInfo formInfo, String taskId, String processInstanceId, String processDefinitionId, String tenantId, String outcome) {
return commandExecutor.execute(new CustomCreateFormInstanceCmd(formInfo, variables, taskId, processInstanceId, processDefinitionId, tenantId, outcome));
}
@Override
public Map<String, Object> getVariablesFromFormSubmission(FormInfo formInfo, Map<String, Object> values) {
return commandExecutor.execute(new CustomGetVariablesFromFormSubmissionCmd(formInfo, values));
}
@Override
public Map<String, Object> getVariablesFromFormSubmission(FormInfo formInfo, Map<String, Object> values, String outcome) {
return commandExecutor.execute(new CustomGetVariablesFromFormSubmissionCmd(formInfo, values, outcome));
}
}

View File

@ -0,0 +1,158 @@
package cn.axzo.workflow.form.engine.cmd;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.form.api.FormInfo;
import org.flowable.form.api.FormInstance;
import org.flowable.form.engine.FormEngineConfiguration;
import org.flowable.form.engine.impl.cmd.CreateFormInstanceCmd;
import org.flowable.form.engine.impl.persistence.entity.FormInstanceEntity;
import org.flowable.form.engine.impl.persistence.entity.FormInstanceEntityManager;
import org.flowable.form.engine.impl.util.CommandContextUtil;
import org.flowable.form.model.FormField;
import org.flowable.form.model.FormFieldTypes;
import org.flowable.form.model.SimpleFormModel;
import org.joda.time.LocalDate;
import org.springframework.util.CollectionUtils;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import static cn.axzo.workflow.common.code.FormInstanceRespCode.FORM_DATA_PARSE_ERROR_BY_UPLOAD;
/**
* 覆盖表单引擎的创建表单实例的命令
*
* @author wangli
* @since 2025-01-22 16:52
*/
public class CustomCreateFormInstanceCmd extends CreateFormInstanceCmd {
public CustomCreateFormInstanceCmd(FormInfo formInfo, Map<String, Object> variables, String taskId, String processInstanceId, String processDefinitionId, String tenantId, String outcome) {
super(formInfo, variables, taskId, processInstanceId, processDefinitionId, tenantId, outcome);
}
public CustomCreateFormInstanceCmd(String formModelId, Map<String, Object> variables, String taskId, String processInstanceId, String processDefinitionId, String tenantId, String outcome) {
super(formModelId, variables, taskId, processInstanceId, processDefinitionId, tenantId, outcome);
}
public CustomCreateFormInstanceCmd(FormInfo formInfo, Map<String, Object> variables, String taskId, String scopeId, String scopeType, String scopeDefinitionId, String tenantId, String outcome) {
super(formInfo, variables, taskId, scopeId, scopeType, scopeDefinitionId, tenantId, outcome);
}
public CustomCreateFormInstanceCmd(String formModelId, Map<String, Object> variables, String taskId, String scopeId, String scopeType, String scopeDefinitionId, String tenantId, String outcome) {
super(formModelId, variables, taskId, scopeId, scopeType, scopeDefinitionId, tenantId, outcome);
}
@Override
public FormInstance execute(CommandContext commandContext) {
FormEngineConfiguration formEngineConfiguration = CommandContextUtil.getFormEngineConfiguration();
if (formInfo == null) {
if (formModelId == null) {
throw new FlowableException("Invalid form model and no form model Id provided");
}
formInfo = CommandContextUtil.getFormEngineConfiguration().getFormRepositoryService().getFormModelById(formModelId);
}
if (formInfo == null || formInfo.getId() == null) {
throw new FlowableException("Invalid form model provided");
}
ObjectMapper objectMapper = formEngineConfiguration.getObjectMapper();
ObjectNode submittedFormValuesJson = objectMapper.createObjectNode();
ObjectNode valuesNode = submittedFormValuesJson.putObject("values");
// Loop over all form fields and see if a value was provided
SimpleFormModel formModel = (SimpleFormModel) formInfo.getFormModel();
Map<String, FormField> fieldMap = formModel.allFieldsAsMap();
for (String fieldId : fieldMap.keySet()) {
FormField formField = fieldMap.get(fieldId);
if (FormFieldTypes.EXPRESSION.equals(formField.getType()) || FormFieldTypes.CONTAINER.equals(formField.getType())) {
continue;
}
if (variables != null && variables.containsKey(fieldId)) {
Object variableValue = variables.get(fieldId);
if (variableValue == null) {
valuesNode.putNull(fieldId);
} else if (variableValue instanceof Long) {
valuesNode.put(fieldId, (Long) variables.get(fieldId));
} else if (variableValue instanceof Double) {
valuesNode.put(fieldId, (Double) variables.get(fieldId));
} else if (variableValue instanceof Boolean) {
valuesNode.put(fieldId, (Boolean) variables.get(fieldId));
} else if (variableValue instanceof LocalDate) {
valuesNode.put(fieldId, ((LocalDate) variableValue).toString());
} else if (variableValue instanceof Collection) {
if (CollectionUtils.isEmpty((Collection<?>) variableValue)) {
variableValue = Collections.emptyList();
}
try {
valuesNode.put(fieldId, objectMapper.writeValueAsString(variableValue));
} catch (JsonProcessingException e) {
throw new WorkflowEngineException(FORM_DATA_PARSE_ERROR_BY_UPLOAD);
}
} else {
valuesNode.put(fieldId, variableValue.toString());
}
}
}
if (outcome != null) {
submittedFormValuesJson.put("flowable_form_outcome", outcome);
}
FormInstanceEntityManager formInstanceEntityManager = CommandContextUtil.getFormInstanceEntityManager(commandContext);
FormInstanceEntity formInstanceEntity = findExistingFormInstance(formEngineConfiguration);
if (formInstanceEntity == null) {
formInstanceEntity = formInstanceEntityManager.create();
}
formInstanceEntity.setFormDefinitionId(formInfo.getId());
formInstanceEntity.setTaskId(taskId);
if (processInstanceId != null) {
formInstanceEntity.setProcessInstanceId(processInstanceId);
formInstanceEntity.setProcessDefinitionId(processDefinitionId);
} else {
formInstanceEntity.setScopeId(scopeId);
formInstanceEntity.setScopeType(scopeType);
formInstanceEntity.setScopeDefinitionId(scopeDefinitionId);
}
formInstanceEntity.setSubmittedDate(formEngineConfiguration.getClock().getCurrentTime());
formInstanceEntity.setSubmittedBy(Authentication.getAuthenticatedUserId());
if (tenantId != null) {
formInstanceEntity.setTenantId(tenantId);
}
try {
formInstanceEntity.setFormValueBytes(objectMapper.writeValueAsBytes(submittedFormValuesJson));
} catch (Exception e) {
throw new FlowableException("Error setting form values JSON", e);
}
if (formInstanceEntity.getId() == null) {
formInstanceEntityManager.insert(formInstanceEntity);
} else {
formInstanceEntityManager.update(formInstanceEntity);
}
return formInstanceEntity;
}
}

View File

@ -0,0 +1,113 @@
package cn.axzo.workflow.form.engine.cmd;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.form.api.FormInfo;
import org.flowable.form.engine.FormEngineConfiguration;
import org.flowable.form.engine.impl.cmd.GetVariablesFromFormSubmissionCmd;
import org.flowable.form.engine.impl.util.CommandContextUtil;
import org.flowable.form.model.FormField;
import org.flowable.form.model.FormFieldTypes;
import org.joda.time.LocalDate;
import java.util.Date;
import java.util.Map;
/**
* 覆盖表单引擎自带的表单提交处理命令
*
* @author wangli
* @since 2025-01-22 15:20
*/
public class CustomGetVariablesFromFormSubmissionCmd extends GetVariablesFromFormSubmissionCmd {
public CustomGetVariablesFromFormSubmissionCmd(FormInfo formInfo, Map<String, Object> values) {
super(formInfo, values);
}
public CustomGetVariablesFromFormSubmissionCmd(FormInfo formInfo, Map<String, Object> values, String outcome) {
super(formInfo, values, outcome);
}
@Override
public Map<String, Object> execute(CommandContext commandContext) {
return super.execute(commandContext);
}
@Override
protected Object transformFormFieldValueToVariableValue(FormField formField, Object formFieldValue) {
Object result = formFieldValue;
if (formField.getType().equals(FormFieldTypes.DATE) && formFieldValue instanceof String) {
if (StringUtils.isNotEmpty((String) formFieldValue)) {
try {
result = LocalDate.parse((String) formFieldValue);
} catch (Exception e) {
e.printStackTrace();
result = null;
}
}
} else if (formField.getType().equals(FormFieldTypes.DATE) && formFieldValue instanceof Date) {
result = new LocalDate(formFieldValue);
} else if (formField.getType().equals(FormFieldTypes.INTEGER) && formFieldValue instanceof String) {
String strFieldValue = (String) formFieldValue;
if (StringUtils.isNotEmpty(strFieldValue) && NumberUtils.isCreatable(strFieldValue)) {
result = Long.valueOf(strFieldValue);
} else {
result = null;
}
} else if (formField.getType().equals(FormFieldTypes.DECIMAL) && formFieldValue instanceof String) {
String strFieldValue = (String) formFieldValue;
if (StringUtils.isNotEmpty(strFieldValue) && NumberUtils.isCreatable(strFieldValue)) {
result = Double.valueOf(strFieldValue);
} else {
result = null;
}
} else if (formField.getType().equals(FormFieldTypes.AMOUNT) && formFieldValue instanceof String) {
try {
result = Double.parseDouble((String) formFieldValue);
} catch (NumberFormatException e) {
result = null;
}
} else if (formField.getType().equals(FormFieldTypes.DROPDOWN) || formField.getType().equals(FormFieldTypes.RADIO_BUTTONS)) {
if (formFieldValue instanceof Map<?, ?>) {
result = ((Map<?, ?>) formFieldValue).get("id");
if (result == null) {
// fallback to name for manual config options
result = ((Map<?, ?>) formFieldValue).get("name");
}
}
} else if (formField.getType().equals(FormFieldTypes.UPLOAD)) {
FormEngineConfiguration formEngineConfiguration = CommandContextUtil.getFormEngineConfiguration();
try {
String json = formEngineConfiguration.getObjectMapper().writeValueAsString(formFieldValue);
result = json;
} catch (JsonProcessingException e) {
result = null;
}
} else if (formField.getType().equals(FormFieldTypes.PEOPLE) || formField.getType().equals(FormFieldTypes.FUNCTIONAL_GROUP)) {
if (formFieldValue instanceof Map<?, ?>) {
Map<String, Object> value = (Map<String, Object>) formFieldValue;
result = value.get("id").toString();
} else {
// Incorrect or empty map, ignore
result = null;
}
}
// Default: no processing needs to be done, can be stored as-is
return result;
}
}

View File

@ -1,6 +1,5 @@
package cn.axzo.workflow.form.service.converter;
import cn.axzo.workflow.common.model.dto.UploadFieldDTO;
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;
@ -8,17 +7,13 @@ 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.FormFieldTypes;
import org.flowable.form.model.SimpleFormModel;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import static org.mapstruct.NullValueCheckStrategy.ALWAYS;
@ -78,24 +73,10 @@ public interface FormInstanceConverter extends EntityConverter<FormInstanceVO, F
return formFieldDTO;
}
default List<String> castList(FormField field) {
List<String> values = new ArrayList<>();
default List<Object> castList(FormField field) {
List<Object> values = new ArrayList<>();
Object value = field.getValue();
if (Objects.isNull(value)) {
return values;
}
if (Objects.equals(field.getType(), FormFieldTypes.UPLOAD)) {
return ((Collection<String>) field.getValue()).stream()
.filter(StringUtils::hasText)
.map(i -> UploadFieldDTO.toObject(i).toString())
.collect(Collectors.toList());
}
if (value instanceof Collection) {
return (List<String>) value;
}
if (value instanceof String) {
values.add((String) value);
}
return values;
}