diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java index ec13680..ac42e3e 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java @@ -235,6 +235,14 @@ public class JsonUtils { } } + public static String getText(JsonNode node, String fieldName) { + if (node == null) { + return null; + } + JsonNode value = node.get(fieldName); + return value != null && !value.isNull() ? value.asText() : null; + } + public static boolean isJson(String text) { return JSONUtil.isTypeJSON(text); } diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java index 4c91611..fc21f70 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java @@ -12,6 +12,7 @@ import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormFi import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; +import com.fasterxml.jackson.databind.JsonNode; import lombok.SneakyThrows; import org.flowable.common.engine.api.delegate.Expression; import org.flowable.common.engine.api.variable.VariableContainer; @@ -27,6 +28,7 @@ import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.TaskInfo; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -245,10 +247,10 @@ public class FlowableUtils { } // 解析表单配置 - Map formFieldsMap = new HashMap<>(); + Map formFieldsMap = new LinkedHashMap<>(); processDefinitionInfo.getFormFields().forEach(formFieldStr -> { - BpmFormFieldVO formField = JsonUtils.parseObject(formFieldStr, BpmFormFieldVO.class); - parseFormField(formField, formFieldsMap); + JsonNode formFieldNode = JsonUtils.parseObject(formFieldStr, JsonNode.class); + parseFormField(formFieldNode, formFieldsMap); }); // 情况一:当自定义了摘要 @@ -275,18 +277,32 @@ public class FlowableUtils { /** * 递归解析表单字段 */ - private static void parseFormField(BpmFormFieldVO formField, Map formFieldsMap) { - if (formField == null) { + private static void parseFormField(JsonNode formFieldNode, Map formFieldsMap) { + if (formFieldNode == null || !formFieldNode.isObject()) { return; } - // 如果存在 children -> 说明是布局组件 - if (formField.getChildren() != null && !formField.getChildren().isEmpty()) { - for (BpmFormFieldVO child : formField.getChildren()) { + + // 如果 children 里存在对象节点,说明是布局组件;字符串节点是分割线、标签、文字等展示组件内容,直接跳过。 + JsonNode children = formFieldNode.get("children"); + if (children != null && children.isArray() && children.size() > 0) { + boolean hasObjectChild = false; + for (JsonNode child : children) { + if (!child.isObject()) { + continue; + } + hasObjectChild = true; parseFormField(child, formFieldsMap); } - return; + if (hasObjectChild) { + return; + } } + // 真实字段才加入 map + BpmFormFieldVO formField = new BpmFormFieldVO() + .setType(JsonUtils.getText(formFieldNode, "type")) + .setField(JsonUtils.getText(formFieldNode, "field")) + .setTitle(JsonUtils.getText(formFieldNode, "title")); if (StrUtil.isNotBlank(formField.getField())) { formFieldsMap.put(formField.getField(), formField); } diff --git a/yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtilsTest.java b/yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtilsTest.java new file mode 100644 index 0000000..e1bebe1 --- /dev/null +++ b/yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtilsTest.java @@ -0,0 +1,167 @@ +package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; + +import cn.iocoder.yudao.framework.common.core.KeyValue; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; +import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * {@link FlowableUtils} 的单元测试。 + * + * @author 芋道源码 + */ +class FlowableUtilsTest { + + @Test + public void testGetSummary_customSummary_parseDbFormFields() { + // 准备参数:模拟 DB 中 form_fields 字段,列表里每个元素都是一个 form-create 字段 JSON。 + BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionInfo(dbFormFields(), + summarySetting(true, "reason", "days", "notExists", "startTime")); + Map processVariables = processVariables(); + + // 调用 + List> summary = FlowableUtils.getSummary(processDefinitionInfo, processVariables); + + // 断言 + assertEquals(Arrays.asList( + new KeyValue<>("请假原因", "事假"), + new KeyValue<>("请假天数", "3"), + new KeyValue<>("开始时间", "2026-05-31 09:00:00")), + summary); + } + + @Test + public void testGetSummary_defaultSummary_parseFirstThreeFieldsByFormOrder() { + // 准备参数:未开启自定义摘要时,默认取表单配置顺序里的前三个真实字段。 + BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionInfo(dbFormFields(), null); + Map processVariables = processVariables(); + + // 调用 + List> summary = FlowableUtils.getSummary(processDefinitionInfo, processVariables); + + // 断言 + assertEquals(Arrays.asList( + new KeyValue<>("请假原因", "事假"), + new KeyValue<>("开始时间", "2026-05-31 09:00:00"), + new KeyValue<>("请假天数", "3")), + summary); + } + + @Test + public void testGetSummary_summaryDisabled_useDefaultSummary() { + // 准备参数:摘要设置存在但未启用时,仍走默认摘要逻辑。 + BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionInfo(dbFormFields(), + summarySetting(false, "remark")); + Map processVariables = processVariables(); + + // 调用 + List> summary = FlowableUtils.getSummary(processDefinitionInfo, processVariables); + + // 断言 + assertEquals(Arrays.asList( + new KeyValue<>("请假原因", "事假"), + new KeyValue<>("开始时间", "2026-05-31 09:00:00"), + new KeyValue<>("请假天数", "3")), + summary); + } + + @Test + public void testGetSummary_displayComponentsOnly_returnEmpty() { + // 准备参数:分割线、标签、文字等展示组件的 children 是字符串数组,不是表单字段对象。 + BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionInfo(Arrays.asList( + DIVIDER_FIELD, + TEXT_FIELD, + TAG_FIELD), null); + + // 调用 + List> summary = FlowableUtils.getSummary(processDefinitionInfo, + Collections.emptyMap()); + + // 断言 + assertEquals(Collections.emptyList(), summary); + } + + @Test + public void testGetSummary_notNormalForm_returnNull() { + // 准备参数 + BpmProcessDefinitionInfoDO processDefinitionInfo = BpmProcessDefinitionInfoDO.builder() + .formType(BpmModelFormTypeEnum.CUSTOM.getType()) + .build(); + + // 调用 & 断言 + assertNull(FlowableUtils.getSummary(null, Collections.emptyMap())); + assertNull(FlowableUtils.getSummary(processDefinitionInfo, Collections.emptyMap())); + } + + private static BpmProcessDefinitionInfoDO processDefinitionInfo(List formFields, + BpmModelMetaInfoVO.SummarySetting summarySetting) { + return BpmProcessDefinitionInfoDO.builder() + .formType(BpmModelFormTypeEnum.NORMAL.getType()) + .formFields(formFields) + .summarySetting(summarySetting) + .build(); + } + + private static BpmModelMetaInfoVO.SummarySetting summarySetting(Boolean enable, String... fields) { + BpmModelMetaInfoVO.SummarySetting summarySetting = new BpmModelMetaInfoVO.SummarySetting(); + summarySetting.setEnable(enable); + summarySetting.setSummary(Arrays.asList(fields)); + return summarySetting; + } + + private static List dbFormFields() { + return Arrays.asList( + DIVIDER_FIELD, + "{\"type\":\"input\",\"field\":\"reason\",\"title\":\"请假原因\",\"value\":\"\"," + + "\"props\":{\"type\":\"textarea\",\"placeholder\":\"请输入请假原因\"}," + + "\"$required\":\"请输入请假原因\",\"_fc_id\":\"id_F1\",\"_fc_drag_tag\":\"input\"," + + "\"hidden\":false,\"display\":true}", + TEXT_FIELD, + "{\"type\":\"elRow\",\"title\":\"栅格布局\",\"children\":[" + + "{\"type\":\"elCol\",\"props\":{\"span\":12},\"children\":[" + + "{\"type\":\"DatePicker\",\"field\":\"startTime\",\"title\":\"开始时间\"," + + "\"props\":{\"type\":\"datetime\",\"placeholder\":\"请选择开始时间\"}," + + "\"_fc_id\":\"id_F2\",\"_fc_drag_tag\":\"datePicker\"}]}," + + "\"字段说明\"," + + "{\"type\":\"elCol\",\"props\":{\"span\":12},\"children\":[" + + "{\"type\":\"inputNumber\",\"field\":\"days\",\"title\":\"请假天数\"," + + "\"props\":{\"min\":0,\"precision\":1},\"_fc_id\":\"id_F3\"," + + "\"_fc_drag_tag\":\"inputNumber\"}]}],\"_fc_id\":\"id_LAYOUT\"," + + "\"_fc_drag_tag\":\"row\"}", + TAG_FIELD, + "{\"type\":\"input\",\"field\":\"remark\",\"title\":\"备注\",\"value\":\"\"," + + "\"props\":{\"placeholder\":\"请输入备注\"},\"_fc_id\":\"id_F4\"," + + "\"_fc_drag_tag\":\"input\",\"hidden\":false,\"display\":true}"); + } + + private static Map processVariables() { + Map processVariables = new HashMap<>(); + processVariables.put("reason", "事假"); + processVariables.put("startTime", "2026-05-31 09:00:00"); + processVariables.put("days", 3); + processVariables.put("remark", "下午到家"); + return processVariables; + } + + private static final String DIVIDER_FIELD = "{\"type\":\"elDivider\",\"children\":[\"基础信息\"]," + + "\"props\":{\"contentPosition\":\"left\"},\"_fc_id\":\"id_DIVIDER\"," + + "\"_fc_drag_tag\":\"elDivider\"}"; + + private static final String TEXT_FIELD = "{\"type\":\"div\",\"children\":[\"请按实际情况填写\"]," + + "\"props\":{\"style\":{\"color\":\"#909399\"}},\"_fc_id\":\"id_TEXT\"," + + "\"_fc_drag_tag\":\"text\"}"; + + private static final String TAG_FIELD = "{\"type\":\"elTag\",\"children\":[\"重要\"]," + + "\"props\":{\"type\":\"warning\"},\"_fc_id\":\"id_TAG\",\"_fc_drag_tag\":\"elTag\"}"; + +}