|
@@ -1,26 +1,20 @@
|
|
|
package com.qqflow.engine.common.service;
|
|
package com.qqflow.engine.common.service;
|
|
|
|
|
|
|
|
|
|
+import com.alibaba.excel.EasyExcel;
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
import com.qqflow.engine.common.exception.BusinessException;
|
|
import com.qqflow.engine.common.exception.BusinessException;
|
|
|
import com.qqflow.engine.domain.flow.mapper.ProcessDefinitionMapper;
|
|
import com.qqflow.engine.domain.flow.mapper.ProcessDefinitionMapper;
|
|
|
import com.qqflow.engine.domain.flow.po.ProcessDefinition;
|
|
import com.qqflow.engine.domain.flow.po.ProcessDefinition;
|
|
|
|
|
+import com.qqflow.engine.domain.system.entity.SysUser;
|
|
|
|
|
+import com.qqflow.engine.domain.system.mapper.SysUserMapper;
|
|
|
import lombok.RequiredArgsConstructor;
|
|
import lombok.RequiredArgsConstructor;
|
|
|
-import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
|
|
|
|
-import org.apache.poi.ss.usermodel.Cell;
|
|
|
|
|
-import org.apache.poi.ss.usermodel.CellType;
|
|
|
|
|
-import org.apache.poi.ss.usermodel.DateUtil;
|
|
|
|
|
-import org.apache.poi.ss.usermodel.Row;
|
|
|
|
|
-import org.apache.poi.ss.usermodel.Sheet;
|
|
|
|
|
-import org.apache.poi.ss.usermodel.Workbook;
|
|
|
|
|
-import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
|
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.web.multipart.MultipartFile;
|
|
import org.springframework.web.multipart.MultipartFile;
|
|
|
|
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.ByteArrayOutputStream;
|
|
|
import java.io.IOException;
|
|
import java.io.IOException;
|
|
|
-import java.io.InputStream;
|
|
|
|
|
|
|
+import java.util.ArrayList;
|
|
|
import java.util.Collections;
|
|
import java.util.Collections;
|
|
|
-import java.util.HashMap;
|
|
|
|
|
import java.util.LinkedHashMap;
|
|
import java.util.LinkedHashMap;
|
|
|
import java.util.List;
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
import java.util.Map;
|
|
@@ -33,15 +27,19 @@ public class ExcelParseService {
|
|
|
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
|
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
|
|
|
|
|
|
|
private final ProcessDefinitionMapper processDefinitionMapper;
|
|
private final ProcessDefinitionMapper processDefinitionMapper;
|
|
|
|
|
+ private final SysUserMapper sysUserMapper;
|
|
|
|
|
|
|
|
- /**
|
|
|
|
|
- * 根据字段映射关系解析 Excel,返回表单数据 Map。
|
|
|
|
|
- *
|
|
|
|
|
- * @param file Excel 文件
|
|
|
|
|
- * @param definitionId 流程定义 ID
|
|
|
|
|
- * @param mappings Excel 列名 -> 系统字段名 的映射
|
|
|
|
|
- */
|
|
|
|
|
|
|
+ /** 单行解析(兼容旧接口) */
|
|
|
public Map<String, Object> parseExcel(MultipartFile file, Long definitionId, Map<String, String> mappings) {
|
|
public Map<String, Object> parseExcel(MultipartFile file, Long definitionId, Map<String, String> mappings) {
|
|
|
|
|
+ List<Map<String, Object>> rows = this.parseExcelRows(file, definitionId, mappings);
|
|
|
|
|
+ if (rows.isEmpty()) {
|
|
|
|
|
+ throw new BusinessException("Excel 无有效数据");
|
|
|
|
|
+ }
|
|
|
|
|
+ return rows.get(0);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 批量解析 Excel,返回每行数据的 Map 列表 */
|
|
|
|
|
+ public List<Map<String, Object>> parseExcelRows(MultipartFile file, Long definitionId, Map<String, String> mappings) {
|
|
|
if (file == null || file.isEmpty()) {
|
|
if (file == null || file.isEmpty()) {
|
|
|
throw new BusinessException("文件不能为空");
|
|
throw new BusinessException("文件不能为空");
|
|
|
}
|
|
}
|
|
@@ -53,88 +51,109 @@ public class ExcelParseService {
|
|
|
throw new BusinessException("流程定义不存在");
|
|
throw new BusinessException("流程定义不存在");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- try (InputStream is = file.getInputStream();
|
|
|
|
|
- Workbook workbook = createWorkbook(is, file.getOriginalFilename())) {
|
|
|
|
|
- Sheet sheet = workbook.getSheetAt(0);
|
|
|
|
|
- if (sheet.getPhysicalNumberOfRows() < 2) {
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ List<Object> rawRows = EasyExcel.read(file.getInputStream()).headRowNumber(0).sheet(0).doReadSync();
|
|
|
|
|
+ if (rawRows.size() < 2) {
|
|
|
throw new BusinessException("Excel 至少需要包含表头和一行数据");
|
|
throw new BusinessException("Excel 至少需要包含表头和一行数据");
|
|
|
}
|
|
}
|
|
|
- Row headerRow = sheet.getRow(0);
|
|
|
|
|
- if (headerRow == null) {
|
|
|
|
|
- throw new BusinessException("Excel 表头为空");
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- // 读取表头:列索引 -> Excel 列名
|
|
|
|
|
- Map<Integer, String> columnIndexMap = new HashMap<>();
|
|
|
|
|
- for (int i = 0; i < headerRow.getLastCellNum(); i++) {
|
|
|
|
|
- Cell cell = headerRow.getCell(i);
|
|
|
|
|
- if (cell == null) continue;
|
|
|
|
|
- String value = getCellStringValue(cell);
|
|
|
|
|
- if (value != null && !value.isBlank()) {
|
|
|
|
|
- columnIndexMap.put(i, value.trim());
|
|
|
|
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
|
|
+ Map<Integer, String> headerRow = (Map<Integer, String>) rawRows.get(0);
|
|
|
|
|
+
|
|
|
|
|
+ String nodeFieldName = definition.getNodeFieldName();
|
|
|
|
|
+ String ownerFieldName = definition.getOwnerFieldName();
|
|
|
|
|
+ String bizFieldName = definition.getBizFieldName();
|
|
|
|
|
+
|
|
|
|
|
+ List<Map<String, Object>> results = new ArrayList<>();
|
|
|
|
|
+ // 从第二行开始(索引 1)遍历数据行
|
|
|
|
|
+ for (int rowIdx = 1; rowIdx < rawRows.size(); rowIdx++) {
|
|
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
|
|
+ Map<Integer, String> dataRow = (Map<Integer, String>) rawRows.get(rowIdx);
|
|
|
|
|
+ // 跳过全空行
|
|
|
|
|
+ if (dataRow.values().stream().allMatch(v -> v == null || v.isBlank())) continue;
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> formData = new LinkedHashMap<>();
|
|
|
|
|
+ String nodeName = null;
|
|
|
|
|
+ String ownerName = null;
|
|
|
|
|
+ String bizValue = null;
|
|
|
|
|
+
|
|
|
|
|
+ for (Map.Entry<Integer, String> entry : headerRow.entrySet()) {
|
|
|
|
|
+ int col = entry.getKey();
|
|
|
|
|
+ String excelColumn = entry.getValue() != null ? entry.getValue().trim() : "";
|
|
|
|
|
+ if (excelColumn.isEmpty()) continue;
|
|
|
|
|
+
|
|
|
|
|
+ String value = dataRow.get(col);
|
|
|
|
|
+ String systemField = mappings.get(excelColumn);
|
|
|
|
|
+
|
|
|
|
|
+ if (nodeFieldName != null && !nodeFieldName.isBlank() && excelColumn.equals(nodeFieldName)) {
|
|
|
|
|
+ nodeName = value;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (ownerFieldName != null && !ownerFieldName.isBlank() && excelColumn.equals(ownerFieldName)) {
|
|
|
|
|
+ ownerName = value;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (systemField != null && !systemField.isBlank()) {
|
|
|
|
|
+ formData.put(systemField, value);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (bizFieldName != null && !bizFieldName.isBlank() && excelColumn.equals(bizFieldName)) {
|
|
|
|
|
+ bizValue = value;
|
|
|
|
|
+ formData.put(bizFieldName, value);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 读取第一行数据
|
|
|
|
|
- Row dataRow = sheet.getRow(1);
|
|
|
|
|
- if (dataRow == null) {
|
|
|
|
|
- throw new BusinessException("Excel 数据行为空");
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- Map<String, Object> formData = new LinkedHashMap<>();
|
|
|
|
|
- for (Map.Entry<Integer, String> entry : columnIndexMap.entrySet()) {
|
|
|
|
|
- String excelColumn = entry.getValue();
|
|
|
|
|
- String systemField = mappings.get(excelColumn);
|
|
|
|
|
- if (systemField == null || systemField.isBlank()) {
|
|
|
|
|
- continue;
|
|
|
|
|
|
|
+ Long ownerId = null;
|
|
|
|
|
+ if (ownerName != null && !ownerName.isBlank()) {
|
|
|
|
|
+ SysUser user = sysUserMapper.selectByRealName(ownerName.trim());
|
|
|
|
|
+ if (user != null) {
|
|
|
|
|
+ ownerId = user.getId();
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- Cell cell = dataRow.getCell(entry.getKey());
|
|
|
|
|
- Object value = getCellValue(cell);
|
|
|
|
|
- formData.put(systemField, value);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> rowResult = new LinkedHashMap<>();
|
|
|
|
|
+ rowResult.put("formData", formData);
|
|
|
|
|
+ rowResult.put("nodeName", nodeName);
|
|
|
|
|
+ rowResult.put("ownerId", ownerId);
|
|
|
|
|
+ rowResult.put("ownerName", ownerName);
|
|
|
|
|
+ rowResult.put("bizValue", bizValue);
|
|
|
|
|
+ results.add(rowResult);
|
|
|
}
|
|
}
|
|
|
- return formData;
|
|
|
|
|
|
|
+ return results;
|
|
|
} catch (IOException e) {
|
|
} catch (IOException e) {
|
|
|
throw new BusinessException("Excel 解析失败: " + e.getMessage());
|
|
throw new BusinessException("Excel 解析失败: " + e.getMessage());
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /**
|
|
|
|
|
- * 生成 Excel 模板文件字节数组
|
|
|
|
|
- */
|
|
|
|
|
|
|
+ /** 生成 Excel 模板 */
|
|
|
public byte[] generateTemplate(Long definitionId) {
|
|
public byte[] generateTemplate(Long definitionId) {
|
|
|
ProcessDefinition definition = this.processDefinitionMapper.selectById(definitionId);
|
|
ProcessDefinition definition = this.processDefinitionMapper.selectById(definitionId);
|
|
|
if (definition == null) {
|
|
if (definition == null) {
|
|
|
throw new BusinessException("流程定义不存在");
|
|
throw new BusinessException("流程定义不存在");
|
|
|
}
|
|
}
|
|
|
- String formSchema = definition.getFormSchema();
|
|
|
|
|
- List<Map<String, Object>> fields = parseFormSchema(formSchema);
|
|
|
|
|
-
|
|
|
|
|
- try (Workbook workbook = new XSSFWorkbook()) {
|
|
|
|
|
- Sheet sheet = workbook.createSheet("模板");
|
|
|
|
|
- // 表头行
|
|
|
|
|
- Row headerRow = sheet.createRow(0);
|
|
|
|
|
- for (int i = 0; i < fields.size(); i++) {
|
|
|
|
|
- Map<String, Object> field = fields.get(i);
|
|
|
|
|
- String label = Objects.toString(field.get("label"), "");
|
|
|
|
|
- String name = Objects.toString(field.get("name"), "");
|
|
|
|
|
- Cell cell = headerRow.createCell(i);
|
|
|
|
|
- cell.setCellValue(label + "(" + name + ")");
|
|
|
|
|
- }
|
|
|
|
|
- // 示例数据行
|
|
|
|
|
- Row exampleRow = sheet.createRow(1);
|
|
|
|
|
- for (int i = 0; i < fields.size(); i++) {
|
|
|
|
|
- Cell cell = exampleRow.createCell(i);
|
|
|
|
|
- cell.setCellValue("示例值");
|
|
|
|
|
- }
|
|
|
|
|
- // 自动调整列宽
|
|
|
|
|
- for (int i = 0; i < fields.size(); i++) {
|
|
|
|
|
- sheet.autoSizeColumn(i);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ List<Map<String, Object>> fields = parseFormSchema(definition.getFormSchema());
|
|
|
|
|
+ String nodeFieldName = definition.getNodeFieldName();
|
|
|
|
|
+ String ownerFieldName = definition.getOwnerFieldName();
|
|
|
|
|
|
|
|
- try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
|
|
|
|
|
- workbook.write(bos);
|
|
|
|
|
- return bos.toByteArray();
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ List<List<String>> head = new ArrayList<>();
|
|
|
|
|
+ if (nodeFieldName != null && !nodeFieldName.isBlank()) {
|
|
|
|
|
+ head.add(Collections.singletonList(nodeFieldName));
|
|
|
|
|
+ }
|
|
|
|
|
+ if (ownerFieldName != null && !ownerFieldName.isBlank()) {
|
|
|
|
|
+ head.add(Collections.singletonList(ownerFieldName));
|
|
|
|
|
+ }
|
|
|
|
|
+ for (Map<String, Object> field : fields) {
|
|
|
|
|
+ String label = Objects.toString(field.get("label"), Objects.toString(field.get("name"), ""));
|
|
|
|
|
+ head.add(Collections.singletonList(label));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ List<List<String>> data = new ArrayList<>();
|
|
|
|
|
+ List<String> exampleRow = new ArrayList<>();
|
|
|
|
|
+ for (int i = 0; i < head.size(); i++) {
|
|
|
|
|
+ exampleRow.add("示例值");
|
|
|
|
|
+ }
|
|
|
|
|
+ data.add(exampleRow);
|
|
|
|
|
+
|
|
|
|
|
+ try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
|
|
|
|
|
+ EasyExcel.write(bos).head(head).sheet("模板").doWrite(data);
|
|
|
|
|
+ return bos.toByteArray();
|
|
|
} catch (IOException e) {
|
|
} catch (IOException e) {
|
|
|
throw new BusinessException("模板生成失败: " + e.getMessage());
|
|
throw new BusinessException("模板生成失败: " + e.getMessage());
|
|
|
}
|
|
}
|
|
@@ -151,51 +170,4 @@ public class ExcelParseService {
|
|
|
throw new BusinessException("表单字段定义格式错误");
|
|
throw new BusinessException("表单字段定义格式错误");
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- private Workbook createWorkbook(InputStream is, String filename) throws IOException {
|
|
|
|
|
- if (filename != null && filename.toLowerCase().endsWith(".xls")) {
|
|
|
|
|
- return new HSSFWorkbook(is);
|
|
|
|
|
- }
|
|
|
|
|
- return new XSSFWorkbook(is);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private String getCellStringValue(Cell cell) {
|
|
|
|
|
- if (cell == null) return "";
|
|
|
|
|
- return switch (cell.getCellType()) {
|
|
|
|
|
- case STRING -> cell.getStringCellValue();
|
|
|
|
|
- case NUMERIC -> {
|
|
|
|
|
- if (DateUtil.isCellDateFormatted(cell)) {
|
|
|
|
|
- yield cell.getLocalDateTimeCellValue().toString();
|
|
|
|
|
- }
|
|
|
|
|
- double num = cell.getNumericCellValue();
|
|
|
|
|
- if (num == Math.rint(num)) {
|
|
|
|
|
- yield String.valueOf((long) num);
|
|
|
|
|
- }
|
|
|
|
|
- yield String.valueOf(num);
|
|
|
|
|
- }
|
|
|
|
|
- case BOOLEAN -> String.valueOf(cell.getBooleanCellValue());
|
|
|
|
|
- case FORMULA -> cell.getCellFormula();
|
|
|
|
|
- default -> "";
|
|
|
|
|
- };
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private Object getCellValue(Cell cell) {
|
|
|
|
|
- if (cell == null) return "";
|
|
|
|
|
- return switch (cell.getCellType()) {
|
|
|
|
|
- case STRING -> cell.getStringCellValue();
|
|
|
|
|
- case NUMERIC -> {
|
|
|
|
|
- if (DateUtil.isCellDateFormatted(cell)) {
|
|
|
|
|
- yield cell.getLocalDateTimeCellValue().toString();
|
|
|
|
|
- }
|
|
|
|
|
- double num = cell.getNumericCellValue();
|
|
|
|
|
- if (num == Math.rint(num)) {
|
|
|
|
|
- yield (long) num;
|
|
|
|
|
- }
|
|
|
|
|
- yield num;
|
|
|
|
|
- }
|
|
|
|
|
- case BOOLEAN -> cell.getBooleanCellValue();
|
|
|
|
|
- case FORMULA -> getCellStringValue(cell);
|
|
|
|
|
- default -> "";
|
|
|
|
|
- };
|
|
|
|
|
- }
|
|
|
|
|
}
|
|
}
|