소스 검색

fix(import): 修复导入流程的负责人与解析逻辑

- 修复未登录时 ownerId.equals(loginUser.getUserId()) 的 NPE
- 导入结果新增 failCount / failReasons,失败行不再静默跳过
- Excel 单元格统一按 Object -> String 转换,避免数字/日期列 ClassCastException
- startInstanceAtNode 只按名称匹配审批节点,防止导入到条件/CC 节点
- 模板生成增加表头重复检测,冲突时给出明确错误提示
- 后端编译通过
ye-zhaojia 1 주 전
부모
커밋
c0b0e6f3df

+ 16 - 6
src/main/java/com/qqflow/engine/common/service/ExcelParseService.java

@@ -58,7 +58,7 @@ public class ExcelParseService {
             }
 
             @SuppressWarnings("unchecked")
-            Map<Integer, String> headerRow = (Map<Integer, String>) rawRows.get(0);
+            Map<Integer, Object> headerRow = (Map<Integer, Object>) rawRows.get(0);
 
             String nodeFieldName = definition.getNodeFieldName();
             String ownerFieldName = definition.getOwnerFieldName();
@@ -68,21 +68,22 @@ public class ExcelParseService {
             // 从第二行开始(索引 1)遍历数据行
             for (int rowIdx = 1; rowIdx < rawRows.size(); rowIdx++) {
                 @SuppressWarnings("unchecked")
-                Map<Integer, String> dataRow = (Map<Integer, String>) rawRows.get(rowIdx);
+                Map<Integer, Object> dataRow = (Map<Integer, Object>) rawRows.get(rowIdx);
                 // 跳过全空行
-                if (dataRow.values().stream().allMatch(v -> v == null || v.isBlank())) continue;
+                if (dataRow.values().stream().allMatch(v -> v == null || String.valueOf(v).trim().isEmpty())) continue;
 
                 Map<String, Object> formData = new LinkedHashMap<>();
                 String nodeName = null;
                 String ownerName = null;
                 String bizValue = null;
 
-                for (Map.Entry<Integer, String> entry : headerRow.entrySet()) {
+                for (Map.Entry<Integer, Object> entry : headerRow.entrySet()) {
                     int col = entry.getKey();
-                    String excelColumn = entry.getValue() != null ? entry.getValue().trim() : "";
+                    String excelColumn = entry.getValue() != null ? String.valueOf(entry.getValue()).trim() : "";
                     if (excelColumn.isEmpty()) continue;
 
-                    String value = dataRow.get(col);
+                    Object cellValue = dataRow.get(col);
+                    String value = cellValue == null ? "" : String.valueOf(cellValue).trim();
                     String systemField = mappings.get(excelColumn);
 
                     if (nodeFieldName != null && !nodeFieldName.isBlank() && excelColumn.equals(nodeFieldName)) {
@@ -133,14 +134,23 @@ public class ExcelParseService {
         String ownerFieldName = definition.getOwnerFieldName();
 
         List<List<String>> head = new ArrayList<>();
+        java.util.Set<String> usedHeaders = new java.util.HashSet<>();
         if (nodeFieldName != null && !nodeFieldName.isBlank()) {
             head.add(Collections.singletonList(nodeFieldName));
+            usedHeaders.add(nodeFieldName);
         }
         if (ownerFieldName != null && !ownerFieldName.isBlank()) {
+            if (!usedHeaders.add(ownerFieldName)) {
+                throw new BusinessException("模板表头重复:" + ownerFieldName + ",请检查流程定义的节点字段/负责人字段配置");
+            }
             head.add(Collections.singletonList(ownerFieldName));
         }
         for (Map<String, Object> field : fields) {
             String label = Objects.toString(field.get("label"), Objects.toString(field.get("name"), ""));
+            if (label.isBlank()) continue;
+            if (!usedHeaders.add(label)) {
+                throw new BusinessException("模板表头重复:" + label + ",请检查表单字段 label 是否与节点字段/负责人字段同名");
+            }
             head.add(Collections.singletonList(label));
         }
 

+ 40 - 26
src/main/java/com/qqflow/engine/domain/flow/controller/FlowImportController.java

@@ -97,15 +97,19 @@ public class FlowImportController {
                     + java.time.LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("MMddHHmm"));
 
             List<Long> instanceIds = new ArrayList<>();
+            List<String> failReasons = new ArrayList<>();
             int successCount = 0;
             int updateCount = 0;
             for (int i = 0; i < rows.size(); i++) {
+                int rowNo = i + 2; // Excel 第 1 行是表头,数据行从第 2 行开始
                 Map<String, Object> row = rows.get(i);
                 @SuppressWarnings("unchecked")
                 Map<String, Object> formData = (Map<String, Object>) row.get("formData");
                 String nodeName = (String) row.get("nodeName");
                 Long ownerId = (Long) row.get("ownerId");
+                String ownerName = (String) row.get("ownerName");
                 if (Objects.isNull(ownerId)) {
+                    failReasons.add("第" + rowNo + "行:未找到用户【" + ownerName + "】");
                     continue;
                 }
                 String bizValue = (String) row.get("bizValue");
@@ -113,45 +117,55 @@ public class FlowImportController {
                 Long effectiveOwnerId = null;
                 if (isAdmin) {
                     effectiveOwnerId = ownerId;
-                } else if (ownerId.equals(loginUser.getUserId())) {
+                } else if (loginUser != null && ownerId.equals(loginUser.getUserId())) {
                     effectiveOwnerId = ownerId;
                 }
-                if (effectiveOwnerId == null) continue;
+                if (effectiveOwnerId == null) {
+                    failReasons.add("第" + rowNo + "行:无权为【" + ownerName + "】导入流程");
+                    continue;
+                }
 
-                if (bizValue != null && !bizValue.isBlank()) {
-                    ProcessInstance existing = this.processInstanceMapper.selectByInstanceNo(bizValue);
-                    if (existing != null) {
-                        this.processInstanceService.updateFormData(
-                                existing.getId(), mapper.writeValueAsString(formData), nodeName);
-                        instanceIds.add(existing.getId());
-                        updateCount++;
-                        continue;
+                try {
+                    if (bizValue != null && !bizValue.isBlank()) {
+                        ProcessInstance existing = this.processInstanceMapper.selectByInstanceNo(bizValue);
+                        if (existing != null) {
+                            this.processInstanceService.updateFormData(
+                                    existing.getId(), mapper.writeValueAsString(formData), nodeName);
+                            instanceIds.add(existing.getId());
+                            updateCount++;
+                            continue;
+                        }
                     }
-                }
 
-                StartProcessDTO dto = new StartProcessDTO();
-                dto.setProcessDefinitionId(definitionId);
-                dto.setTitle(titlePrefix + "-" + (i + 1));
-                dto.setFormData(mapper.writeValueAsString(formData));
-                if (bizValue != null && !bizValue.isBlank()) {
-                    dto.setInstanceNo(bizValue);
-                }
-                dto.setApplicantId(effectiveOwnerId);
+                    StartProcessDTO dto = new StartProcessDTO();
+                    dto.setProcessDefinitionId(definitionId);
+                    dto.setTitle(titlePrefix + "-" + (i + 1));
+                    dto.setFormData(mapper.writeValueAsString(formData));
+                    if (bizValue != null && !bizValue.isBlank()) {
+                        dto.setInstanceNo(bizValue);
+                    }
+                    dto.setApplicantId(effectiveOwnerId);
 
-                Long instanceId;
-                if (nodeName != null && !nodeName.isBlank()) {
-                    instanceId = this.processInstanceService.startProcessAtNode(dto, nodeName);
-                } else {
-                    instanceId = this.processInstanceService.startProcess(dto);
+                    Long instanceId;
+                    if (nodeName != null && !nodeName.isBlank()) {
+                        instanceId = this.processInstanceService.startProcessAtNode(dto, nodeName);
+                    } else {
+                        instanceId = this.processInstanceService.startProcess(dto);
+                    }
+                    instanceIds.add(instanceId);
+                    successCount++;
+                } catch (Exception e) {
+                    String msg = e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName();
+                    failReasons.add("第" + rowNo + "行:" + msg);
                 }
-                instanceIds.add(instanceId);
-                successCount++;
             }
 
             Map<String, Object> response = new LinkedHashMap<>();
             response.put("total", rows.size());
             response.put("successCount", successCount);
             response.put("updateCount", updateCount);
+            response.put("failCount", failReasons.size());
+            response.put("failReasons", failReasons);
             response.put("instanceIds", instanceIds);
             return Result.ok(response);
         } catch (com.fasterxml.jackson.core.JsonProcessingException e) {

+ 2 - 1
src/main/java/com/qqflow/engine/domain/flow/service/impl/FlowEngineServiceImpl.java

@@ -247,9 +247,10 @@ public class FlowEngineServiceImpl implements FlowEngineService {
             return;
         }
         FlowModel model = this.parseModel(definition.getModelJson());
-        // 按名称匹配目标节点
+        // 按名称匹配目标节点,且必须是审批节点
         FlowNode targetNode = safeNodes(model).stream()
                 .filter(n -> nodeName.equals(n.getName()))
+                .filter(n -> NodeType.APPROVAL.getCode().equals(n.getType()))
                 .findFirst()
                 .orElse(null);
         if (targetNode == null) {