Commit 688de72367b886cef4bd6bbdfc57f9c8c4ce090c

Authored by YunaiV
1 parent af9b6253

fix(bpm): 修复流程摘要解析展示组件时报错

- 表单字段 JSON 改为按 JsonNode 逐个节点递归解析
- 跳过分割线、标签、文字等展示组件的字符串 children
- 默认摘要按表单字段配置顺序取前三个真实字段
- 补充流程摘要解析单元测试,覆盖 DB form_fields 常见格式

对应 https://t.zsxq.com/x1UrW
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java
@@ -235,6 +235,14 @@ public class JsonUtils { @@ -235,6 +235,14 @@ public class JsonUtils {
235 } 235 }
236 } 236 }
237 237
  238 + public static String getText(JsonNode node, String fieldName) {
  239 + if (node == null) {
  240 + return null;
  241 + }
  242 + JsonNode value = node.get(fieldName);
  243 + return value != null && !value.isNull() ? value.asText() : null;
  244 + }
  245 +
238 public static boolean isJson(String text) { 246 public static boolean isJson(String text) {
239 return JSONUtil.isTypeJSON(text); 247 return JSONUtil.isTypeJSON(text);
240 } 248 }
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 @@ -12,6 +12,7 @@ import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormFi
12 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; 12 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
13 import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum; 13 import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
14 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; 14 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
  15 +import com.fasterxml.jackson.databind.JsonNode;
15 import lombok.SneakyThrows; 16 import lombok.SneakyThrows;
16 import org.flowable.common.engine.api.delegate.Expression; 17 import org.flowable.common.engine.api.delegate.Expression;
17 import org.flowable.common.engine.api.variable.VariableContainer; 18 import org.flowable.common.engine.api.variable.VariableContainer;
@@ -27,6 +28,7 @@ import org.flowable.engine.runtime.ProcessInstance; @@ -27,6 +28,7 @@ import org.flowable.engine.runtime.ProcessInstance;
27 import org.flowable.task.api.TaskInfo; 28 import org.flowable.task.api.TaskInfo;
28 29
29 import java.util.HashMap; 30 import java.util.HashMap;
  31 +import java.util.LinkedHashMap;
30 import java.util.List; 32 import java.util.List;
31 import java.util.Map; 33 import java.util.Map;
32 import java.util.Objects; 34 import java.util.Objects;
@@ -245,10 +247,10 @@ public class FlowableUtils { @@ -245,10 +247,10 @@ public class FlowableUtils {
245 } 247 }
246 248
247 // 解析表单配置 249 // 解析表单配置
248 - Map<String, BpmFormFieldVO> formFieldsMap = new HashMap<>(); 250 + Map<String, BpmFormFieldVO> formFieldsMap = new LinkedHashMap<>();
249 processDefinitionInfo.getFormFields().forEach(formFieldStr -> { 251 processDefinitionInfo.getFormFields().forEach(formFieldStr -> {
250 - BpmFormFieldVO formField = JsonUtils.parseObject(formFieldStr, BpmFormFieldVO.class);  
251 - parseFormField(formField, formFieldsMap); 252 + JsonNode formFieldNode = JsonUtils.parseObject(formFieldStr, JsonNode.class);
  253 + parseFormField(formFieldNode, formFieldsMap);
252 }); 254 });
253 255
254 // 情况一:当自定义了摘要 256 // 情况一:当自定义了摘要
@@ -275,18 +277,32 @@ public class FlowableUtils { @@ -275,18 +277,32 @@ public class FlowableUtils {
275 /** 277 /**
276 * 递归解析表单字段 278 * 递归解析表单字段
277 */ 279 */
278 - private static void parseFormField(BpmFormFieldVO formField, Map<String, BpmFormFieldVO> formFieldsMap) {  
279 - if (formField == null) { 280 + private static void parseFormField(JsonNode formFieldNode, Map<String, BpmFormFieldVO> formFieldsMap) {
  281 + if (formFieldNode == null || !formFieldNode.isObject()) {
280 return; 282 return;
281 } 283 }
282 - // 如果存在 children -> 说明是布局组件  
283 - if (formField.getChildren() != null && !formField.getChildren().isEmpty()) {  
284 - for (BpmFormFieldVO child : formField.getChildren()) { 284 +
  285 + // 如果 children 里存在对象节点,说明是布局组件;字符串节点是分割线、标签、文字等展示组件内容,直接跳过。
  286 + JsonNode children = formFieldNode.get("children");
  287 + if (children != null && children.isArray() && children.size() > 0) {
  288 + boolean hasObjectChild = false;
  289 + for (JsonNode child : children) {
  290 + if (!child.isObject()) {
  291 + continue;
  292 + }
  293 + hasObjectChild = true;
285 parseFormField(child, formFieldsMap); 294 parseFormField(child, formFieldsMap);
286 } 295 }
287 - return; 296 + if (hasObjectChild) {
  297 + return;
  298 + }
288 } 299 }
  300 +
289 // 真实字段才加入 map 301 // 真实字段才加入 map
  302 + BpmFormFieldVO formField = new BpmFormFieldVO()
  303 + .setType(JsonUtils.getText(formFieldNode, "type"))
  304 + .setField(JsonUtils.getText(formFieldNode, "field"))
  305 + .setTitle(JsonUtils.getText(formFieldNode, "title"));
290 if (StrUtil.isNotBlank(formField.getField())) { 306 if (StrUtil.isNotBlank(formField.getField())) {
291 formFieldsMap.put(formField.getField(), formField); 307 formFieldsMap.put(formField.getField(), formField);
292 } 308 }
yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtilsTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.KeyValue;
  4 +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
  5 +import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
  6 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
  7 +import org.junit.jupiter.api.Test;
  8 +
  9 +import java.util.Arrays;
  10 +import java.util.Collections;
  11 +import java.util.HashMap;
  12 +import java.util.List;
  13 +import java.util.Map;
  14 +
  15 +import static org.junit.jupiter.api.Assertions.assertEquals;
  16 +import static org.junit.jupiter.api.Assertions.assertNull;
  17 +
  18 +/**
  19 + * {@link FlowableUtils} 的单元测试。
  20 + *
  21 + * @author 芋道源码
  22 + */
  23 +class FlowableUtilsTest {
  24 +
  25 + @Test
  26 + public void testGetSummary_customSummary_parseDbFormFields() {
  27 + // 准备参数:模拟 DB 中 form_fields 字段,列表里每个元素都是一个 form-create 字段 JSON。
  28 + BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionInfo(dbFormFields(),
  29 + summarySetting(true, "reason", "days", "notExists", "startTime"));
  30 + Map<String, Object> processVariables = processVariables();
  31 +
  32 + // 调用
  33 + List<KeyValue<String, String>> summary = FlowableUtils.getSummary(processDefinitionInfo, processVariables);
  34 +
  35 + // 断言
  36 + assertEquals(Arrays.asList(
  37 + new KeyValue<>("请假原因", "事假"),
  38 + new KeyValue<>("请假天数", "3"),
  39 + new KeyValue<>("开始时间", "2026-05-31 09:00:00")),
  40 + summary);
  41 + }
  42 +
  43 + @Test
  44 + public void testGetSummary_defaultSummary_parseFirstThreeFieldsByFormOrder() {
  45 + // 准备参数:未开启自定义摘要时,默认取表单配置顺序里的前三个真实字段。
  46 + BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionInfo(dbFormFields(), null);
  47 + Map<String, Object> processVariables = processVariables();
  48 +
  49 + // 调用
  50 + List<KeyValue<String, String>> summary = FlowableUtils.getSummary(processDefinitionInfo, processVariables);
  51 +
  52 + // 断言
  53 + assertEquals(Arrays.asList(
  54 + new KeyValue<>("请假原因", "事假"),
  55 + new KeyValue<>("开始时间", "2026-05-31 09:00:00"),
  56 + new KeyValue<>("请假天数", "3")),
  57 + summary);
  58 + }
  59 +
  60 + @Test
  61 + public void testGetSummary_summaryDisabled_useDefaultSummary() {
  62 + // 准备参数:摘要设置存在但未启用时,仍走默认摘要逻辑。
  63 + BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionInfo(dbFormFields(),
  64 + summarySetting(false, "remark"));
  65 + Map<String, Object> processVariables = processVariables();
  66 +
  67 + // 调用
  68 + List<KeyValue<String, String>> summary = FlowableUtils.getSummary(processDefinitionInfo, processVariables);
  69 +
  70 + // 断言
  71 + assertEquals(Arrays.asList(
  72 + new KeyValue<>("请假原因", "事假"),
  73 + new KeyValue<>("开始时间", "2026-05-31 09:00:00"),
  74 + new KeyValue<>("请假天数", "3")),
  75 + summary);
  76 + }
  77 +
  78 + @Test
  79 + public void testGetSummary_displayComponentsOnly_returnEmpty() {
  80 + // 准备参数:分割线、标签、文字等展示组件的 children 是字符串数组,不是表单字段对象。
  81 + BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionInfo(Arrays.asList(
  82 + DIVIDER_FIELD,
  83 + TEXT_FIELD,
  84 + TAG_FIELD), null);
  85 +
  86 + // 调用
  87 + List<KeyValue<String, String>> summary = FlowableUtils.getSummary(processDefinitionInfo,
  88 + Collections.emptyMap());
  89 +
  90 + // 断言
  91 + assertEquals(Collections.emptyList(), summary);
  92 + }
  93 +
  94 + @Test
  95 + public void testGetSummary_notNormalForm_returnNull() {
  96 + // 准备参数
  97 + BpmProcessDefinitionInfoDO processDefinitionInfo = BpmProcessDefinitionInfoDO.builder()
  98 + .formType(BpmModelFormTypeEnum.CUSTOM.getType())
  99 + .build();
  100 +
  101 + // 调用 & 断言
  102 + assertNull(FlowableUtils.getSummary(null, Collections.emptyMap()));
  103 + assertNull(FlowableUtils.getSummary(processDefinitionInfo, Collections.emptyMap()));
  104 + }
  105 +
  106 + private static BpmProcessDefinitionInfoDO processDefinitionInfo(List<String> formFields,
  107 + BpmModelMetaInfoVO.SummarySetting summarySetting) {
  108 + return BpmProcessDefinitionInfoDO.builder()
  109 + .formType(BpmModelFormTypeEnum.NORMAL.getType())
  110 + .formFields(formFields)
  111 + .summarySetting(summarySetting)
  112 + .build();
  113 + }
  114 +
  115 + private static BpmModelMetaInfoVO.SummarySetting summarySetting(Boolean enable, String... fields) {
  116 + BpmModelMetaInfoVO.SummarySetting summarySetting = new BpmModelMetaInfoVO.SummarySetting();
  117 + summarySetting.setEnable(enable);
  118 + summarySetting.setSummary(Arrays.asList(fields));
  119 + return summarySetting;
  120 + }
  121 +
  122 + private static List<String> dbFormFields() {
  123 + return Arrays.asList(
  124 + DIVIDER_FIELD,
  125 + "{\"type\":\"input\",\"field\":\"reason\",\"title\":\"请假原因\",\"value\":\"\","
  126 + + "\"props\":{\"type\":\"textarea\",\"placeholder\":\"请输入请假原因\"},"
  127 + + "\"$required\":\"请输入请假原因\",\"_fc_id\":\"id_F1\",\"_fc_drag_tag\":\"input\","
  128 + + "\"hidden\":false,\"display\":true}",
  129 + TEXT_FIELD,
  130 + "{\"type\":\"elRow\",\"title\":\"栅格布局\",\"children\":["
  131 + + "{\"type\":\"elCol\",\"props\":{\"span\":12},\"children\":["
  132 + + "{\"type\":\"DatePicker\",\"field\":\"startTime\",\"title\":\"开始时间\","
  133 + + "\"props\":{\"type\":\"datetime\",\"placeholder\":\"请选择开始时间\"},"
  134 + + "\"_fc_id\":\"id_F2\",\"_fc_drag_tag\":\"datePicker\"}]},"
  135 + + "\"字段说明\","
  136 + + "{\"type\":\"elCol\",\"props\":{\"span\":12},\"children\":["
  137 + + "{\"type\":\"inputNumber\",\"field\":\"days\",\"title\":\"请假天数\","
  138 + + "\"props\":{\"min\":0,\"precision\":1},\"_fc_id\":\"id_F3\","
  139 + + "\"_fc_drag_tag\":\"inputNumber\"}]}],\"_fc_id\":\"id_LAYOUT\","
  140 + + "\"_fc_drag_tag\":\"row\"}",
  141 + TAG_FIELD,
  142 + "{\"type\":\"input\",\"field\":\"remark\",\"title\":\"备注\",\"value\":\"\","
  143 + + "\"props\":{\"placeholder\":\"请输入备注\"},\"_fc_id\":\"id_F4\","
  144 + + "\"_fc_drag_tag\":\"input\",\"hidden\":false,\"display\":true}");
  145 + }
  146 +
  147 + private static Map<String, Object> processVariables() {
  148 + Map<String, Object> processVariables = new HashMap<>();
  149 + processVariables.put("reason", "事假");
  150 + processVariables.put("startTime", "2026-05-31 09:00:00");
  151 + processVariables.put("days", 3);
  152 + processVariables.put("remark", "下午到家");
  153 + return processVariables;
  154 + }
  155 +
  156 + private static final String DIVIDER_FIELD = "{\"type\":\"elDivider\",\"children\":[\"基础信息\"],"
  157 + + "\"props\":{\"contentPosition\":\"left\"},\"_fc_id\":\"id_DIVIDER\","
  158 + + "\"_fc_drag_tag\":\"elDivider\"}";
  159 +
  160 + private static final String TEXT_FIELD = "{\"type\":\"div\",\"children\":[\"请按实际情况填写\"],"
  161 + + "\"props\":{\"style\":{\"color\":\"#909399\"}},\"_fc_id\":\"id_TEXT\","
  162 + + "\"_fc_drag_tag\":\"text\"}";
  163 +
  164 + private static final String TAG_FIELD = "{\"type\":\"elTag\",\"children\":[\"重要\"],"
  165 + + "\"props\":{\"type\":\"warning\"},\"_fc_id\":\"id_TAG\",\"_fc_drag_tag\":\"elTag\"}";
  166 +
  167 +}