Forráskód Böngészése

批量导入EXCEL导入模板

cr 4 napja
szülő
commit
4d65520f10

+ 20 - 0
blade-service/blade-manager/pom.xml

@@ -214,6 +214,26 @@
             <version>2.10.3</version>
             <scope>compile</scope>
         </dependency>
+        <dependency>
+            <groupId>e-iceblue</groupId>
+            <artifactId>spire.xls</artifactId>
+            <version>15.9.1</version>
+        </dependency>
+        <dependency>
+            <groupId>xalan</groupId>
+            <artifactId>xalan</artifactId>
+            <version>2.7.2</version>
+        </dependency>
+        <dependency>
+            <groupId>xerces</groupId>
+            <artifactId>xercesImpl</artifactId>
+            <version>2.12.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jsoup</groupId>
+            <artifactId>jsoup</artifactId>
+            <version>1.16.1</version>
+        </dependency>
     </dependencies>
     <build>
         <plugins>

+ 362 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/controller/WbsTreeContractController.java

@@ -39,6 +39,7 @@ import org.springblade.manager.feign.ContractClient;
 import org.springblade.manager.service.IWbsParamService;
 import org.springblade.manager.service.IWbsTreeContractService;
 import org.springblade.manager.service.impl.WbsTreeContractServiceImpl;
+import org.springblade.manager.util.DataStructureFormatUtils;
 import org.springblade.manager.utils.FileUtils;
 import org.springblade.manager.utils.RandomNumberHolder;
 import org.springblade.manager.vo.*;
@@ -79,6 +80,7 @@ import static com.aspose.cells.LoadDataFilterOptions.FORMULA;
 import static com.aspose.cells.PropertyType.BOOLEAN;
 import static com.aspose.cells.PropertyType.STRING;
 import static java.sql.Types.NUMERIC;
+import static java.util.stream.Collectors.toMap;
 
 
 @RestController
@@ -682,7 +684,365 @@ public class WbsTreeContractController extends BladeController {
         cellStyle.setBorderRight(BorderStyle.THIN);
         cellStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());
     }
+    @PostMapping("/import-node-excel")
+    @ApiOperationSupport(order = 13)
+    @ApiOperation(value = "客户端-导入多sheet excel到对应节点下的表单", notes = "传入节点ID、分类和多sheet excel文件")
+    public R<Map<String, Object>> importNodeExcel(
+            @RequestPart MultipartFile file,
+            @RequestParam Long nodeId,
+            @RequestParam Integer classify) throws Exception {
+
+        // 1. 获取节点下所有表单,并建立 sheet名 -> WbsTreeContract 的映射(处理特殊字符)
+        List<WbsTreeContract> wbsTreeContracts = wbsTreeContractServiceImpl.selectAllPkeyIdByNodeId(nodeId, classify);
+        if (wbsTreeContracts.isEmpty()) {
+            return R.fail("该节点下没有找到对应的表单数据");
+        }
+
+        // 处理表单名称(与下载时的sheet名处理逻辑一致,确保匹配)
+        Map<String, WbsTreeContract> nodeNameToContractMap = new HashMap<>();
+        for (WbsTreeContract contract : wbsTreeContracts) {
+            String processedNodeName = contract.getNodeName().replaceAll("[\\\\/:*?\"<>|]", "_");
+            nodeNameToContractMap.put(processedNodeName, contract);
+        }
+
+        // 2. 加载上传的多sheet Excel文件
+        com.spire.xls.Workbook mainWorkbook = new com.spire.xls.Workbook();
+        try {
+            mainWorkbook.loadFromStream(file.getInputStream());
+        } catch (Exception e) {
+            logger.error("加载Excel文件失败", e);
+            return R.fail("Excel文件解析失败:" + e.getMessage());
+        }
+
+        // 3. 遍历所有sheet,逐个处理
+        Map<String, Object> allResults = new LinkedHashMap<>(); // 按sheet顺序保存结果
+        int sheetCount = mainWorkbook.getWorksheets().getCount();
+
+        for (int i = 0; i < sheetCount; i++) {
+            com.spire.xls.Worksheet sheet = mainWorkbook.getWorksheets().get(i);
+            String sheetName = sheet.getName();
+            String processedSheetName = sheetName.replaceAll("[\\\\/:*?\"<>|]", "_"); // 处理sheet名特殊字符
+
+            // 匹配对应的表单
+            WbsTreeContract matchedContract = nodeNameToContractMap.get(processedSheetName);
+            if (matchedContract == null) {
+                allResults.put(sheetName, "未找到匹配的表单(节点名:" + sheetName + ")");
+                logger.warn("sheet名[{}]未匹配到任何表单,已跳过", sheetName);
+                continue;
+            }
+
+            // 获取当前表单的pkeyId
+            String pkeyId = matchedContract.getPKeyId() + "";
+            logger.info("开始处理sheet[{}],对应表单pkeyId[{}]", sheetName, pkeyId);
+
+            // 4. 将当前sheet保存为临时Excel文件(模拟单个文件上传)
+            File tempFile = null;
+            InputStream tempInputStream = null;
+            try {
+                // 创建临时文件
+                tempFile = File.createTempFile("sheet_", ".xlsx");
+                // 创建仅包含当前sheet的新工作簿
+                com.spire.xls.Workbook singleSheetWorkbook = new com.spire.xls.Workbook();
+                singleSheetWorkbook.getWorksheets().clear();
+                singleSheetWorkbook.getWorksheets().addCopy(sheet); // 复制当前sheet到新工作簿
+                singleSheetWorkbook.saveToFile(tempFile.getAbsolutePath(), com.spire.xls.FileFormat.Version2016);
+                singleSheetWorkbook.dispose();
+
+                // 读取临时文件作为输入流,调用单表单导入逻辑
+                tempInputStream = new FileInputStream(tempFile);
+                Map<String, Object> sheetResult = processSingleSheetImport(pkeyId, tempInputStream);
+                Map<String, String> dataMap = getDataMap(sheetResult);
+                String delSql = "delete from " + matchedContract.getInitTableName() + " where p_key_id=" + matchedContract.getPKeyId();
+                dataMap.put("p_key_id", matchedContract.getPKeyId()+"");
+                String  sqlInfo = buildMTableInsertSql(matchedContract.getInitTableName(), dataMap, SnowFlakeUtil.getId(), null, null).toString();
+                jdbcTemplate.execute(delSql);
+                jdbcTemplate.execute(sqlInfo);
+            } catch (Exception e) {
+                String errorMsg = "处理sheet[" + sheetName + "]失败:" + e.getMessage();
+                allResults.put(sheetName, errorMsg);
+                logger.error(errorMsg, e);
+            } finally {
+                // 关闭流并删除临时文件
+                if (tempInputStream != null) {
+                    tempInputStream.close();
+                }
+                if (tempFile != null && !tempFile.delete()) {
+                    logger.warn("临时文件[{}]删除失败", tempFile.getAbsolutePath());
+                }
+            }
+        }
+        mainWorkbook.dispose();
+        return R.data(allResults);
+    }
+
+    public StringBuilder buildMTableInsertSql(String tabName, Map<String, String> dataMap2, Object id, Object groupId, Object pKeyId) {
+        if (dataMap2 == null || dataMap2.isEmpty() || tabName == null || tabName.isEmpty()) {
+            return new StringBuilder();
+        }
+        //拼接SQL
+        StringBuilder sql = new StringBuilder("INSERT INTO " + tabName),
+                keySql = new StringBuilder(),
+                valSql = new StringBuilder();
+        if (id == null) {
+            if (dataMap2.containsKey("id")) {
+                id = dataMap2.get("id");
+            }
+        }
+        keySql.append("id");
+        valSql.append(id == null ? SnowFlakeUtil.getId() : id);
+        if (groupId ==  null) {
+            groupId = dataMap2.get("group_id");
+        }
+        if (groupId != null) {
+            keySql.append(", group_id");
+            valSql.append(", ").append(groupId);
+        }
+        if (pKeyId == null) {
+            pKeyId = dataMap2.get("p_key_id");
+        }
+        if (pKeyId != null) {
+            keySql.append(", p_key_id");
+            valSql.append(", ").append(pKeyId);
+        }
+        //参数
+        Map<String, String> opsParamMap = new HashMap<>();
+        dataMap2.remove("id");
+        dataMap2.remove("group_id");
+        dataMap2.remove("p_key_id");
+        String key201 = dataMap2.remove("key_201");
+        String fields = dataMap2.keySet().stream().map(key -> "'" + key + "'").collect(Collectors.joining(","));
+        Map<String, Integer> map = new HashMap<>();
+        if (!fields.isEmpty()) {
+            try {
+                fields = fields + ", 'key_201'";
+                List<Map<String, Object>> fieldMap = jdbcTemplate.queryForList("select distinct COLUMN_NAME as fieldName, CHARACTER_MAXIMUM_LENGTH as fieldLength from information_schema.COLUMNS where  TABLE_NAME = '" + tabName +
+                        "' and COLUMN_NAME in (" + fields + ")");
+                map = fieldMap.stream().collect(toMap(k -> k.get("fieldName") + "", v -> {
+                    try {
+                        return Integer.parseInt(v.get("fieldLength") + "");
+                    } catch (Exception e) {
+                        return 0;
+                    }
+                }, Math::min));
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+        if (key201 != null) {
+            Map<String, String> map1 = DataStructureFormatUtils.parseDataByKey(key201);
+            if (!map1.isEmpty()) {
+                opsParamMap.putAll(map1);
+            }
+        }
+        for (String key : dataMap2.keySet()) {
+            String[] split = key.split("_");
+            if (split.length > 1 && Integer.parseInt(split[1]) > 80) {
+                // 大于80则保留在扩展字段中
+                opsParamMap.put(key, dataMap2.get(key));
+            } else {
+                String value = dataMap2.get(key);
+                if (value != null) {
+                    Integer i = map.get(key);
+                    // 长度超过数据库长度也保留在扩展字段中
+                    if (i != null &&  value.length() > i) {
+                        opsParamMap.put(key, dataMap2.get(key));
+                        continue;
+                    }
+                }
+                keySql.append(", ").append(key);
+                valSql.append(", '").append(value).append("'");
+            }
+        }
+        if (!opsParamMap.isEmpty()) {
+            keySql.append(", key_201");
+            String data = DataStructureFormatUtils.buildData(opsParamMap);
+            try {
+                if (!map.containsKey( "key_201")) {
+                    jdbcTemplate.execute("alter table " + tabName + " add column key_201 text");
+                } else  {
+                    Integer i = map.get("key_201");
+                    if (data.length() > i) {
+                        if (i < 10000) {
+                            // 65535 byte
+                            jdbcTemplate.execute("alter table " + tabName + " modify column key_201 text");
+                        }else {
+                            // 16777215 byte
+                            jdbcTemplate.execute("alter table " + tabName + " modify column key_201 mediumtext");
+                        }
+                    }
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+            valSql.append(", '").append(data).append("'");
+        }
+        sql.append("(").append(keySql).append(")").append(" values(").append(valSql).append(")");
+        return sql;
+    }
+
+    public Map<String,String> getDataMap(Map<String, Object> originalMap){
+        // 用于存储合并后的结果
+        Map<String, String> mergedMap = new HashMap<>();
+
+        // 正则表达式:匹配"key_前缀__剩余坐标"的格式
+        Pattern pattern = Pattern.compile("key_(\\w+)__([\\w_]+)");
+
+        for (Map.Entry<String, Object> entry : originalMap.entrySet()) {
+            String key = entry.getKey();
+            String value = entry.getValue()+"";
+            Matcher matcher = pattern.matcher(key);
+
+            if (matcher.matches()) {
+                String prefix = matcher.group(1); // 提取__前面的前缀(如7、17)
+                String suffix = matcher.group(2); // 提取__后面的剩余坐标(如11_9、12_5)
+
+                // 构建合并后的键(简化为prefix,如7、17)
+                String mergedKey = "key_" + prefix;
+                // 构建合并后的值(值_^_剩余坐标)
+                String mergedValue = value + "_^_" + suffix;
+
+                // 若该键已存在,用☆拼接新值;否则直接存入
+                if (mergedMap.containsKey(mergedKey)) {
+                    mergedMap.put(mergedKey, mergedMap.get(mergedKey) + "☆" + mergedValue);
+                } else {
+                    mergedMap.put(mergedKey, mergedValue);
+                }
+            }
+        }
+        return mergedMap;
+    }
+    /**
+     * 复用importExcel的核心逻辑,处理单个sheet的导入
+     * 抽取自原importExcel方法,参数改为pkeyId和输入流
+     */
+    private Map<String, Object> processSingleSheetImport(String pkeyId, InputStream inputStream) throws Exception {
+        // 获取当前表htmlString(模板)
+        String htmlString_1 = wbsTreeContractServiceImpl.getHtmlString(pkeyId);
+        if (StringUtils.isEmpty(htmlString_1)) {
+            throw new ServiceException("获取表单[" + pkeyId + "]的html模板失败");
+        }
+
+        // 结果集
+        Map<String, Object> resultMap = new HashMap<>();
+
+        // 日期格式正则(复用原逻辑)
+        String doubleSlashRegex_XG = ".*\\/[^\\/]*\\/.*";
+        String dateFormatRegex_yyyyMdd = "\\d{4}/\\d{1,2}/\\d{1,2}";
+        String dateFormatRegex_yyyyMMdd = "\\d{4}/\\d{2}/\\d{2}";
+        String dateFormatRegex_chinese = "(\\d{4}年\\d{1,2}月\\d{1,2}日|\\d{4}年\\d{2}月\\d{2}日)";
+        SimpleDateFormat inputDateFormat = new SimpleDateFormat("yyyy/M/dd");
+        SimpleDateFormat outputDateFormat = new SimpleDateFormat("yyyy年MM月dd日");
+
+        // 临时文件路径(复用原逻辑)
+        Long id = SnowFlakeUtil.getId();
+        String importExcelFilePath = FileUtils.getSysLocalFileUrl();
+        String importExcelTOHtmlPath = importExcelFilePath + "/pdf//" + id + ".html";
+
+        com.spire.xls.Workbook workbook = null;
+        try {
+            // 导入的excel转换为html(复用原逻辑)
+            workbook = new com.spire.xls.Workbook();
+            workbook.loadFromHtml(inputStream); // 加载单个sheet的输入流
+            workbook.saveToFile(importExcelTOHtmlPath, com.spire.xls.FileFormat.HTML);
+            com.spire.xls.Worksheet sheet = workbook.getWorksheets().get(0);
+
+            // 获取转换后的html路径
+            String url_1 = importExcelTOHtmlPath.split("pdf//")[0];
+            String excelToHtmlFileUrl = url_1 + "/pdf/" + id + "_files/" + sheet.getName() + ".html";
+            String htmlString_2 = IoUtil.readToString(new FileInputStream(ResourceUtil.getFile(excelToHtmlFileUrl)));
+
+            // 解析两张html的tr、td(复用原逻辑)
+            Document doc_1 = Jsoup.parse(htmlString_1); // 模板html
+            Document doc_2 = Jsoup.parse(htmlString_2); // 导入的excel转换的html
+            Elements trElements1 = doc_1.select("table tbody tr");
+            Elements trElements2 = doc_2.select("table tbody tr");
+
+            for (int i = 0; i < trElements1.size(); i++) {
+                Element tr1 = trElements1.get(i);
+                Element tr2 = trElements2.size() > i ? trElements2.get(i) : null;
+                if (tr2 == null) break;
+
+                Elements tdElements1 = tr1.select("td");
+                Elements tdElements2 = tr2.select("td");
+
+                for (int j = 0; j < tdElements1.size(); j++) {
+                    Element td1 = tdElements1.get(j);
+                    if (td1.attr("dqid").length() > 0) { // 跳过包含dqid的td
+                        continue;
+                    }
+
+                    Element td2 = tdElements2.size() > j ? tdElements2.get(j) : null;
+                    if (td2 == null) break;
+
+                    String keyName = getKeyNameFromChildElement(td1); // 复用原方法获取key
+                    if (StringUtils.isNotEmpty(keyName)) {
+                        String divValue = td2.text();
+                        if (StringUtils.isNotEmpty(divValue)) {
+                            // 日期范围处理
+                            if (parseDateRange(divValue).size() == 2) {
+                                resultMap.put(keyName, parseDateRange(divValue));
+                                continue;
+                            }
+
+                            // 日期格式转换(复用原逻辑)
+                            Pattern pattern_XG = Pattern.compile(doubleSlashRegex_XG);
+                            Matcher matcher_XG = pattern_XG.matcher(divValue);
+                            if (matcher_XG.matches()) {
+                                Pattern pattern_yyyyMdd = Pattern.compile(dateFormatRegex_yyyyMdd);
+                                Pattern pattern_yyyyMMdd = Pattern.compile(dateFormatRegex_yyyyMMdd);
+                                Matcher matcher_yyyyMdd = pattern_yyyyMdd.matcher(divValue);
+                                Matcher matcher_yyyyMMdd = pattern_yyyyMMdd.matcher(divValue);
 
+                                if (matcher_yyyyMdd.matches() || matcher_yyyyMMdd.matches()) {
+                                    Date date = inputDateFormat.parse(divValue);
+                                    divValue = outputDateFormat.format(date);
+                                }
+                            } else if (divValue.contains("年") && divValue.contains("月") && divValue.contains("日")) {
+                                Pattern pattern_chinese = Pattern.compile(dateFormatRegex_chinese);
+                                Matcher matcher_chinese = pattern_chinese.matcher(divValue);
+                                if (matcher_chinese.matches()) {
+                                    Date date = outputDateFormat.parse(divValue);
+                                    divValue = outputDateFormat.format(date);
+                                }
+                            }
+
+                            resultMap.put(keyName, divValue);
+                        }
+                    }
+                }
+            }
+
+            // 排序结果(复用原逻辑)
+            if (!resultMap.isEmpty()) {
+                List<Map.Entry<String, Object>> entryList = new ArrayList<>(resultMap.entrySet());
+                entryList.sort(new KeyComparator());
+                LinkedHashMap<String, Object> sortedMap = new LinkedHashMap<>();
+                for (Map.Entry<String, Object> entry : entryList) {
+                    sortedMap.put(entry.getKey(), entry.getValue());
+                }
+                resultMap = sortedMap;
+            }
+
+        } finally {
+            if (workbook != null) {
+                workbook.dispose();
+            }
+            // 删除临时文件(复用原逻辑)
+            if (deleteFolder(Paths.get(importExcelTOHtmlPath))) {
+                logger.info("表单[{}]临时文件删除成功", pkeyId);
+            } else {
+                logger.warn("表单[{}]临时文件删除失败", pkeyId);
+            }
+            String url_1 = importExcelTOHtmlPath.split("pdf//")[0];
+            if (deleteFolderAndContents(Paths.get(url_1 + "/pdf/" + id + "_files"))) {
+                logger.info("表单[{}]临时文件夹删除成功", pkeyId);
+            } else {
+                logger.warn("表单[{}]临时文件夹删除失败", pkeyId);
+            }
+        }
+
+        return resultMap.isEmpty() ? Collections.singletonMap("message", "未获取到有效数据") : resultMap;
+    }
     /**
      * 客户端-导入excel数据到对应元素表中
      *
@@ -1145,6 +1505,8 @@ public class WbsTreeContractController extends BladeController {
 
     }
 
+
+
     /**
      * 判断日期范围格式数据,以下12种格式
      * 2023-01-01-2023-01-30 或 2023-01-01~2023-01-30

+ 5 - 1
pom.xml

@@ -226,7 +226,11 @@
             <name>Release Repository</name>
             <url>http://nexus.bladex.vip/repository/maven-releases/</url>
         </repository>
-
+        <repository>
+            <id>com.e-iceblue</id>
+            <name>e-iceblue</name>
+            <url>https://repo.e-iceblue.cn/repository/maven-public/</url>
+        </repository>
     </repositories>
 
     <pluginRepositories>