ソースを参照

Merge branch 'refs/heads/dev' into feature-lihb-20250716

# Conflicts:
#	blade-service/blade-manager/src/main/java/org/springblade/manager/controller/ExcelTabController.java
LHB 3 週間 前
コミット
a47677f0cd

+ 2 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/external/impl/ExternalDataArchiveAutoService.java

@@ -195,6 +195,7 @@ public class ExternalDataArchiveAutoService {
                     localArchive.setOutUrl(externalArchive.getOutUrl());
                     localArchive.setName(externalArchive.getName());
                     localArchive.setFileNumber(externalArchive.getFileNumber());
+                    localArchive.setNodeId(externalArchive.getNodeId());
                     upArchives.add(localArchive);
                 }
             }
@@ -206,6 +207,7 @@ public class ExternalDataArchiveAutoService {
         // 检查关键字段差异
         return !Objects.equals(external.getOutUrl(), local.getOutUrl())
                 || !Objects.equals(external.getName(), local.getName())
+                || !Objects.equals(external.getNodeId(), local.getNodeId())
                 || !Objects.equals(external.getFileNumber(), local.getFileNumber());
 
         // 扩展检查示例(如需其他字段比较,可追加条件):

+ 9 - 1
blade-service/blade-archive/src/main/java/org/springblade/archive/external/impl/ExternalDataArchiveFileService.java

@@ -191,7 +191,14 @@ public class ExternalDataArchiveFileService {
             boolean pdfUrlDifferent = external.getPdfPageUrl() != null &&
                     !Objects.equals(external.getPdfPageUrl(), local.getPdfPageUrl());
 
-            return !Objects.equals(extUtimeStr, localUtimeStr) || pdfUrlDifferent;
+            // 新增nodeId比较:当external的nodeId非空时,与local的nodeId比较
+            boolean nodeIdDifferent = external.getNodeId() != null &&
+                    !Objects.equals(external.getNodeId(), local.getNodeId());
+
+            // 任意字段不同即需要更新
+            return !Objects.equals(extUtimeStr, localUtimeStr)
+                    || pdfUrlDifferent
+                    || nodeIdDifferent; // 新增nodeId判断
         }
     }
 
@@ -210,6 +217,7 @@ public class ExternalDataArchiveFileService {
         local.setFilePage(external.getFilePage());
         local.setFileSize(external.getFileSize());
         local.setUtime(external.getUtime());
+        local.setNodeId(external.getNodeId());
         //local.setArchiveId(external.getArchiveId());
     }
 

+ 10 - 1
blade-service/blade-archive/src/main/java/org/springblade/archive/external/utils/TransUtil.java

@@ -473,7 +473,16 @@ public class TransUtil {
 
 
         // (八)缺陷责任期资料
-        put("8a0aa647278548fa9edba59b78c4", 1945023416096522240L);
+        put("1d4c9460a9954d9d8ffcccc39f78", 1945023416096522240L);
+
+        // 四、施工安全及文明施工文件
+        put("fbca27defd5e48c1b08527154f87", 1927992893478273030L);
+
+        // 五、进度控制文件
+        put("c034b7bf1f4a44b78d422821337d", 1927992893478273031L);
+
+        // 八、施工原始记录
+        put("c52cf546b94d4099b0701a023caa", 1927992893478273037L);
         //二工程管理文件
         //施工准备文件 -1.施工项目部组建、印章启用、人员任命文件,进场人员资质报审文件,施工设备仪器进场报审文件、设备仪器校验、率定文件
         //put("0b0ac82851d7484bba414bb1ccbd", 1892759789415432263L);

+ 113 - 64
blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ArchivesAutoServiceImpl.java

@@ -91,6 +91,7 @@ import javax.servlet.http.HttpServletResponse;
 import java.io.*;
 import java.math.BigDecimal;
 import java.net.URL;
+import java.net.URLEncoder;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -102,6 +103,9 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
+import java.util.zip.Deflater;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
 
 /**
  *  服务实现类
@@ -3701,82 +3705,127 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 	@Override
 	public void batchDownloadFileToZip(String ids, HttpServletResponse response) {
 		List<Long> longs = Func.toLongList(ids);
-		//获取档案下的文件
+		// 获取档案下的文件
 		List<ArchiveFile> result = baseMapper.batchSearchArchiveFile(longs);
-		//判断是否存在文件
-		if (result != null && result.size() > 0) {
-			//获取选择的案卷,只要id和文件提名字段
-			Map<Long, String> nameMap = baseMapper.getArchives(longs).stream().filter(l -> StringUtils.isNotBlank(l.getName())).collect(Collectors.toMap(l -> l.getId(), l -> l.getName()));
-			//默认下载地址
-			String defaultDir = "/www/wwwroot/Users/hongchuangyanfa/Desktop/archiveDownload";
-			//项目下地址
-			String projectDir = defaultDir + "/" + result.get(0).getProjectId();
-			File file = new File(projectDir);
-			//获取项目名称
-			ProjectInfo projectInfo = projectClient.getById(result.get(0).getProjectId());
-			//判断文件夹是否存在,存在则该项目正在下载打包,不存在则生成
-			if (file.exists()) {
-				throw new ServiceException("当前项目正在下载档案,请稍后再试");
-			} else {
-				file.mkdir();
+
+		if (CollectionUtils.isEmpty(result)) {
+			throw new ServiceException("未找到可下载的文件");
+		}
+
+		// 获取选择的案卷名称映射
+		Map<Long, String> nameMap = baseMapper.getArchives(longs).stream()
+				.filter(l -> StringUtils.isNotBlank(l.getName()))
+				.collect(Collectors.toMap(l -> l.getId(), l -> l.getName()));
+
+		// 使用临时目录而不是固定目录,避免并发问题
+		String tempDir = System.getProperty("java.io.tmpdir") + "/archive_download_" + UUID.randomUUID();
+		String projectDir = tempDir + "/" + result.get(0).getProjectId();
+		File projectDirFile = new File(projectDir);
+
+		try {
+			// 创建项目目录
+			if (!projectDirFile.mkdirs()) {
+				throw new ServiceException("无法创建临时目录");
 			}
-			//删除掉pdfUrl为空的数据
+
+			// 获取项目信息
+			ProjectInfo projectInfo = projectClient.getById(result.get(0).getProjectId());
+			String zipFileName = projectInfo.getId() + ".zip";
+
+			// 过滤掉无PDF的文件
 			result.removeIf(query -> StringUtils.isEmpty(query.getPdfFileUrl()));
-			//按照档案id分组
-			Map<Long, List<ArchiveFile>> map = result.stream().collect(Collectors.groupingBy(ArchiveFile::getArchiveId));
-			try {
-				//为每个档案分别设置
-				for (Long archiveId : map.keySet()) {
-					String initName = nameMap.get(archiveId);
-					String[] split = initName.split(")");
-					String name = split[split.length - 1];
-					String archiveDir = projectDir + "/" + name;
-					//创建档案文件夹
-					File file2 = new File(archiveDir);
-					file2.mkdir();
-					//组卷,下载PDF
+
+			// 按照档案id分组
+			Map<Long, List<ArchiveFile>> archiveFilesMap = result.stream()
+					.collect(Collectors.groupingBy(ArchiveFile::getArchiveId));
+
+			// 设置响应头 - 提前设置以便流式传输
+			response.setContentType("application/zip");
+			response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(zipFileName, "UTF-8"));
+
+			// 使用ZipOutputStream直接流式写入响应,避免生成临时zip文件
+			try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
+				// 设置压缩级别
+				zos.setLevel(Deflater.BEST_SPEED);
+
+				// 为每个档案处理文件
+				for (Map.Entry<Long, List<ArchiveFile>> entry : archiveFilesMap.entrySet()) {
+					Long archiveId = entry.getKey();
+					List<ArchiveFile> files = entry.getValue();
+					String archiveName = getArchiveName(nameMap.get(archiveId));
+
+					// 处理合并的档案PDF
 					String mergeArchivesFile = this.getMergeArchivesFile(archiveId);
-					InputStream file_out2 = CommonUtil.getOSSInputStream(mergeArchivesFile);
-					if (file_out2 != null) {
-						CommonUtil.inputStreamToFile(file_out2, new File(archiveDir + "/" + name + ".pdf"));
-						file_out2.close();
+					if (StringUtils.isNotBlank(mergeArchivesFile)) {
+						addFileToZip(zos, archiveName + "/" + archiveName + ".pdf", mergeArchivesFile);
 					}
 
-					//下载卷内文件
-					String archiveFileDir = archiveDir + "/" + "卷内文件";
-					File file3 = new File(archiveFileDir);
-					file3.mkdir();
-					List<ArchiveFile> files = map.get(archiveId);
+					// 处理卷内文件
+					String innerDir = archiveName + "/卷内文件/";
 					for (ArchiveFile archiveFile : files) {
-						String fileUrl = archiveFile.getPdfFileUrl();
-						String initFileName = archiveFile.getFileName();
-						if (initFileName.length() > 100) {
-							initFileName = initFileName.substring(0, 100);
-						}
-						String fileName = "/" + initFileName + ".pdf";
-						InputStream file_out = CommonUtil.getOSSInputStream(fileUrl);
-						if (file_out != null) {
-							CommonUtil.inputStreamToFile(file_out, new File(archiveFileDir + fileName));
-							file_out.close();
-						}
+						String fileName = getSafeFileName(archiveFile.getFileName()) + ".pdf";
+						addFileToZip(zos, innerDir + fileName, archiveFile.getPdfFileUrl());
 					}
 				}
-				//下载完成,打包文件
-				this.packageZip2(defaultDir, projectDir, projectInfo.getId());
-				String zipFile = defaultDir + "/" + projectInfo.getId() + ".zip";
-				Path path = Paths.get(zipFile);
-				response.setContentType("application/zip");
-				response.setHeader("Content-Disposition", "attachment;filename=" + projectInfo.getId() + "2.zip");
-				// 获取文件内容流并写入响应
-				Files.copy(path, response.getOutputStream());
+			}
+		} catch (Exception e) {
+			throw new ServiceException("文件下载失败: " + e.getMessage());
+		} finally {
+			// 清理临时目录
+			deleteDirectory(new File(tempDir));
+		}
+	}
 
-			} catch (Exception e) {
-				throw new ServiceException(e.getMessage());
-			} finally {
-				//删除文件与zip文件
-				this.deleteFile(defaultDir, projectInfo.getId());
+	// 辅助方法:添加文件到zip流(分片下载)
+	private void addFileToZip(ZipOutputStream zos, String entryName, String fileUrl) throws IOException {
+		if (StringUtils.isBlank(fileUrl)) return;
+
+		ZipEntry zipEntry = new ZipEntry(entryName);
+		zos.putNextEntry(zipEntry);
+
+		try (InputStream in = CommonUtil.getOSSInputStream(fileUrl)) {
+			if (in == null) return;
+
+			byte[] buffer = new byte[8192]; // 8KB缓冲区
+			int len;
+			while ((len = in.read(buffer)) > 0) {
+				zos.write(buffer, 0, len);
+			}
+		} finally {
+			zos.closeEntry();
+		}
+	}
+
+	// 辅助方法:安全获取档案名称
+	private String getArchiveName(String initName) {
+		if (StringUtils.isBlank(initName)) return "未命名档案";
+		String[] split = initName.split(")");
+		return split[split.length - 1];
+	}
+
+	// 辅助方法:安全文件名
+	private String getSafeFileName(String fileName) {
+		if (StringUtils.isBlank(fileName)) return "未命名文件";
+		// 限制文件名长度并移除非法字符
+		return fileName.substring(0, Math.min(100, fileName.length()))
+				.replaceAll("[\\\\/:*?\"<>|]", "");
+	}
+
+	// 辅助方法:递归删除目录
+	private void deleteDirectory(File directory) {
+		if (directory == null || !directory.exists()) return;
+
+		File[] files = directory.listFiles();
+		if (files != null) {
+			for (File file : files) {
+				if (file.isDirectory()) {
+					deleteDirectory(file);
+				} else {
+					file.delete();
+				}
 			}
 		}
+		directory.delete();
 	}
 
 	@Override

+ 151 - 30
blade-service/blade-business/src/main/java/org/springblade/business/controller/InformationWriteQueryController.java

@@ -2012,13 +2012,20 @@ public R<Boolean> copyContractTreeNode(@RequestBody CopyContractTreeNodeVO vo) {
             }
 
             //TODO 20250414-lhb-新增 添加祖级字段 ancestorsPId
-            List<WbsTreeContract> contractWbsTreeByContractId = wbsTreeContractClient.getContractWbsTreeByContractId(Long.valueOf(needCopyNode.getContractId()));
-            contractWbsTreeByContractId.addAll(saveList);
-            Map<Long, WbsTreeContract> collect = contractWbsTreeByContractId.stream().collect(Collectors.toMap(WbsTreeContract::getPKeyId, Function.identity()));
-            saveList.forEach(node -> {
-                String correctAncestors = createAncestorsPId(node,collect);;
-                node.setAncestorsPId(correctAncestors);
-            });
+            //因为复制选中节点,所以要查询出选中节点的父节点信息 来组装祖级节点
+            if(needCopyNode != null){
+                Long parentPKeyId = null;
+                String ancestorsPId = null;
+                if(needCopyNode.getPId() == 0L){
+                    ancestorsPId = "0";
+                    parentPKeyId = 0L;
+                }else{
+                    WbsTreeContract parentNode = this.wbsTreeContractClient.getContractNodeByPrimaryKeyId(String.valueOf(needCopyNode.getPId()));
+                    ancestorsPId = parentNode.getAncestorsPId();
+                    parentPKeyId = parentNode.getPKeyId();
+                }
+                attachNodesToTarget(saveList,parentPKeyId,ancestorsPId);
+            }
         }
         needCopyNode.setNodeName(vo.getNeedCopyNodeName());
 
@@ -2186,6 +2193,26 @@ public R<Boolean> copyContractTreeNode(@RequestBody CopyContractTreeNodeVO vo) {
                                     this.addCopyTabData(needCopyNode, toCopyNode, tabOwner, resultTablesData, addTabList, vo.getIsCopyData(), addNewFileTabs);
                                 }
                             }
+
+
+                            /*重构祖级id*/
+                            List<WbsTreeContract> resultAll = new ArrayList<>();
+                            resultAll.addAll(addNodeList);
+                            resultAll.addAll(addTabList);
+                            //因为复制选中节点,所以要查询出选中节点的父节点信息 来组装祖级节点
+                            if(needCopyNode != null && CollectionUtil.isNotEmpty(resultAll)){
+                                Long parentPKeyId = null;
+                                String ancestorsPId = null;
+                                if(needCopyNode.getPId() == 0L){
+                                    ancestorsPId = "0";
+                                    parentPKeyId = 0L;
+                                }else{
+                                    WbsTreeContract parentNode = this.wbsTreeContractClient.getContractNodeByPrimaryKeyId(String.valueOf(toCopyVO.getPrimaryKeyId()));
+                                    ancestorsPId = parentNode.getAncestorsPId();
+                                    parentPKeyId = parentNode.getPKeyId();
+                                }
+                                attachNodesToTarget(resultAll,parentPKeyId,ancestorsPId);
+                            }
                         }
                     }
                 }
@@ -2195,14 +2222,6 @@ public R<Boolean> copyContractTreeNode(@RequestBody CopyContractTreeNodeVO vo) {
                 resultAll.addAll(addNodeList);
                 resultAll.addAll(addTabList);
 
-                //TODO 20250414-lhb-新增
-                List<WbsTreeContract> contractWbsTreeByContractId = wbsTreeContractClient.getContractWbsTreeByContractId(Long.valueOf(contractId));
-                contractWbsTreeByContractId.addAll(resultAll);
-                Map<Long, WbsTreeContract> collect = contractWbsTreeByContractId.stream().collect(Collectors.toMap(WbsTreeContract::getPKeyId, Function.identity()));
-                resultAll.forEach(node -> {
-                    String correctAncestors = createAncestorsPId(node,collect);;
-                    node.setAncestorsPId(correctAncestors);
-                });
 
                 List<WbsTreeContract> allData = this.reBuildAncestors(resultAll);
                 List<WbsTreeContract> nodes = allData.stream().filter(f -> f.getType().equals(1)).collect(Collectors.toList());
@@ -4011,24 +4030,20 @@ public R<Boolean> saveContractTreeNode(@RequestBody AddContractTreeNodeVO vo) {
             saveList.addAll(customResult);
         }
         //找到最顶级节点设置parentId
-        WbsTreeContract topLevelNode = findTopLevelNode(saveList);
-        if (ObjectUtils.isNotEmpty(topLevelNode)) {
+        List<WbsTreeContract> topLevelNode = findTopLevelNode(saveList);
+        if (CollectionUtil.isNotEmpty(topLevelNode)) {
             for (WbsTreeContract wbsTreeContract : saveList) {
-                if (topLevelNode.getPKeyId().equals(wbsTreeContract.getPKeyId())) {
-                    wbsTreeContract.setParentId(treeContract.getId());
-                    //TODO 20250414-lhb-新增
-                    wbsTreeContract.setPId(treeContract.getPKeyId());
+                for (WbsTreeContract contract : topLevelNode) {
+                    if (contract.getPKeyId().equals(wbsTreeContract.getPKeyId())) {
+                        wbsTreeContract.setParentId(treeContract.getId());
+                        //TODO 20250414-lhb-新增
+                        wbsTreeContract.setPId(treeContract.getPKeyId());
+                    }
                 }
             }
         }
         //TODO 20250414-lhb-新增 添加ancestorsPId字段
-        List<WbsTreeContract> contractWbsTreeByContractId = wbsTreeContractClient.getContractWbsTreeByContractId(Long.valueOf(treeContract.getContractId()));
-        contractWbsTreeByContractId.addAll(saveList);
-        Map<Long, WbsTreeContract> collect = contractWbsTreeByContractId.stream().collect(Collectors.toMap(WbsTreeContract::getPKeyId, Function.identity()));
-        saveList.forEach(node -> {
-            String correctAncestors = createAncestorsPId(node,collect);;
-            node.setAncestorsPId(correctAncestors);
-        });
+        attachNodesToTarget(saveList,treeContract.getPKeyId(),treeContract.getAncestorsPId());
 
         R<Boolean> booleanR = this.saveOrCopyNodeTree(saveList, saveLedger, 2, treeContract);
 
@@ -4054,16 +4069,20 @@ public R<String> getDICengNodeName(@RequestParam Long pKeyId,@RequestParam Long
     return R.data("");
 }
 
-public static WbsTreeContract findTopLevelNode(List<WbsTreeContract> saveList) {
+public static List<WbsTreeContract> findTopLevelNode(List<WbsTreeContract> saveList) {
     Set<Long> nodeIds = new HashSet<>();
     for (WbsTreeContract node : saveList) {
         nodeIds.add(node.getId());
     }
+    List<WbsTreeContract> parentNodes = new ArrayList<>();
     for (WbsTreeContract node : saveList) {
         if (!nodeIds.contains(node.getParentId())) {
-            return node;
+            parentNodes.add(node);
         }
     }
+    if(CollectionUtil.isNotEmpty(parentNodes)){
+        return parentNodes;
+    }
     return null;
 }
 
@@ -4938,4 +4957,106 @@ public R<Object> customAddContractNode(@RequestBody CustomAddContractNodeDTO dto
         }
     }
 
+    public void attachNodesToTarget(List<WbsTreeContract> newNodes, Long targetId, String targetAncestors) {
+        // 1. 找到新数据中的顶层节点(新树的根节点)
+        List<WbsTreeContract> newRoot = findRootNode(newNodes);
+
+        // 2. 将新树的根节点绑定到目标节点
+        newRoot.forEach(f -> {
+            f.setPId(targetId);
+            f.setAncestorsPId(calculateAncestors(targetAncestors, targetId));
+
+
+            // 3. 构建映射关系
+            Map<Long, WbsTreeContract> nodeMap = new HashMap<>();
+            Map<Long, List<WbsTreeContract>> childrenMap = new HashMap<>();
+
+            for (WbsTreeContract node : newNodes) {
+                nodeMap.put(node.getPKeyId(), node);
+                childrenMap.computeIfAbsent(node.getPId(), k -> new ArrayList<>()).add(node);
+            }
+
+            // 4. 安全层序遍历(带循环检测)
+            List<WbsTreeContract> list = childrenMap.get(f.getPKeyId());
+            if(CollectionUtil.isNotEmpty(list)){
+                //如果是多个根节点
+                safeBfsTraversal(f, childrenMap, newNodes.size());
+            }
+        });
+
+
+
+    }
+
+    private void safeBfsTraversal(WbsTreeContract root,
+                                  Map<Long, List<WbsTreeContract>> childrenMap,
+                                  int totalNodes) {
+        Queue<WbsTreeContract> queue = new LinkedList<>();
+        Set<Long> visited = new HashSet<>();  // 已访问节点集合
+        queue.add(root);
+        visited.add(root.getPKeyId());
+
+        int processedCount = 0;  // 已处理节点计数器
+        final int MAX_DEPTH = 100;  // 最大深度保护
+
+        while (!queue.isEmpty()) {
+            WbsTreeContract parent = queue.poll();
+            processedCount++;
+
+            // 安全检测1:防止循环引用导致无限循环
+            if (processedCount > totalNodes * 2) {
+                throw new ServiceException("处理节点数超过预期,可能存在循环引用。已处理: "
+                        + processedCount + ",总节点: " + totalNodes);
+            }
+
+            // 安全检测2:防止路径过长
+            if (parent.getAncestorsPId().split(",").length > MAX_DEPTH) {
+                throw new ServiceException("祖级路径超过最大深度限制: " + MAX_DEPTH);
+            }
+
+            List<WbsTreeContract> children = childrenMap.get(parent.getPKeyId());
+            if (children == null) continue;
+
+            for (WbsTreeContract child : children) {
+                // 安全检测3:检查循环引用
+                if (visited.contains(child.getPKeyId())) {
+                    throw new ServiceException("检测到循环引用: 节点" + child.getPKeyId()
+                            + " -> 节点" + parent.getPKeyId());
+                }
+
+                // 更新祖级路径 = 父路径 + 父ID
+                child.setAncestorsPId(calculateAncestors(parent.getAncestorsPId(), parent.getPKeyId()));
+
+                queue.add(child);
+                visited.add(child.getPKeyId());
+            }
+        }
+
+    }
+
+    private String calculateAncestors(String parentAncestors, Long parentId) {
+        if (parentAncestors == null || parentAncestors.isEmpty()) {
+            return parentId.toString();
+        }
+        return parentAncestors + "," + parentId;
+    }
+
+    private List<WbsTreeContract> findRootNode(List<WbsTreeContract> nodes) {
+        Set<Long> nodeIds = new HashSet<>();
+        for (WbsTreeContract node : nodes) {
+            nodeIds.add(node.getPKeyId());
+        }
+        List<WbsTreeContract> parentNodes = new ArrayList<>();
+        for (WbsTreeContract node : nodes) {
+            Long parentId = node.getPId();
+            // 父节点为空 或 父节点不在当前新数据列表中 → 视为根节点
+            if (parentId == null || !nodeIds.contains(parentId)) {
+                parentNodes.add(node);
+            }
+        }
+        if (CollectionUtil.isNotEmpty(parentNodes)) {
+            return parentNodes;
+        }
+        throw new IllegalArgumentException("新数据中未找到根节点");
+    }
 }

+ 1 - 0
blade-service/blade-business/src/main/java/org/springblade/business/feignClient/ArchiveFileClientImpl.java

@@ -330,6 +330,7 @@ public class ArchiveFileClientImpl implements ArchiveFileClient {
                         local.setFilePage(external.getFilePage());
                         local.setFileSize(external.getFileSize());
                         local.setUtime(external.getUtime());
+                        local.setNodeId(external.getNodeId());
                     }
                     return local;
                 })

+ 0 - 1
blade-service/blade-manager/src/main/java/org/springblade/manager/controller/ExcelTabController.java

@@ -2026,7 +2026,6 @@ public class ExcelTabController extends BladeController {
             throw new RuntimeException(e);
         }
 
-
         //公式填充
         executionTime.info("----公式填充执行----");
         this.excelTabService.formulaFillData(tableInfoList, Long.parseLong(nodeId), ExecuteType.INSPECTION);

+ 1 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/TextdictInfoServiceImpl.java

@@ -32,6 +32,7 @@ import org.springblade.manager.service.ISignConfigService;
 import org.springblade.manager.service.ITextdictInfoService;
 import org.springblade.manager.utils.FileUtils;
 import org.springblade.manager.vo.TextdictInfoVO;
+import org.springblade.system.cache.ParamCache;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;

+ 321 - 2
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsTreeContractServiceImpl.java

@@ -82,6 +82,8 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.math.MathContext;
+import java.math.RoundingMode;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Function;
@@ -110,6 +112,8 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
     private final InformationQueryClient informationQueryClient;
     private final ContractClient contractClient;
     private final ITableFileService tableFileService;
+    private final TableInfoMapper  tableInfoMapper;
+    private final WbsFormElementMapper wbsFormElementMapper;
 
 
     @Autowired
@@ -1657,17 +1661,87 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
                     sgTabMaps.put(s + "---" + wbsTreeContract.getPKeyId(), wbsTreeContract.getInitTableName());
                 }
             }
+            //获取 CL08水准测量记录表(监理)的 实际标高、高程偏差 对应字段 m_20220928134702_1574999102784012288
+            String leveling = "CL08水准测量记录表(监理)";
+            String levelingTableName = "m_20220928134702_1574999102784012288";
+
+            String designedElevation = "设计标高";
+            //负值+0/1 正值-0/1
+            String heightDeviation = "高程偏差";
+            //设置标高 + (高度偏差 / 100)
+            String actualElevation = "实际标高";
+
+            //m_20220928134725_1574999197613031424
+            String planePosition = "CL10平面位置检测记录表(监理)";
+            String planePositionTableName = "m_20220928134725_1574999197613031424";
+
+            //负值+0/1 正值-0/1
+            String difference = "差值";
+            //差值X + 差值Y 的开平方
+            String deviation = "偏位";
+            //指定表单和指定字段
+            Map<String, Map<String, String>> dataMaps = new HashMap<>();
+
             for (WbsTreeContract wbsTreeContract : jlTabSort) {
                 //监理表
                 String s = extractAlphameric(wbsTreeContract.getNodeName());
                 if (StringUtils.isNotEmpty(s)) {
                     jlTabMaps.put(s + "---" + wbsTreeContract.getPKeyId(), wbsTreeContract.getInitTableName());
                 }
+
+
+                /**
+                 * 如果是
+                 *  CL08水准测量记录表(监理)
+                 *      1、“实际标高”不获取数据,改为计算得来,计算规则为:实际标高 = 设计标高 + (高程偏差 / 100)。其中高程偏差需带符号进行计算,特别是负号;
+                 *      2、获取的【高程偏差】需要做随机加减(规则为:如果是负数则+0/1,如果是正数则-0/1)
+                 *  CL10平面位置检测记录表(监理)
+                 *      1、“差值”的两列和“偏位”数据不获取
+                 *      2、获取的【差值】两列 需要做随机加减(规则为:如果是负数则+0/1,如果是正数则-0/1)
+                 *      3、“偏差”不获取数据,改为计算得来,计算规则为:(差值x的平方 + 差值y的平方)的和开平方,四舍五入保留整数。
+                 */
+                HashMap<String, String> map = new HashMap<>();
+
+                //判断当前表单是否是CL08、CL10
+                if (levelingTableName.equals(wbsTreeContract.getInitTableName()) || planePositionTableName.equals(wbsTreeContract.getInitTableName())) {
+                    TableInfo tableInfo = tableInfoMapper.selectOne(Wrappers.<TableInfo>lambdaQuery()
+                            .eq(TableInfo::getTabEnName, wbsTreeContract.getInitTableName()));
+                    List<WbsFormElement> wbsFormElements = wbsFormElementMapper.selectList(Wrappers.<WbsFormElement>lambdaQuery()
+                            .eq(WbsFormElement::getFId, tableInfo.getId()));
+                    for (WbsFormElement wbsFormElement : wbsFormElements) {
+                        String eName = wbsFormElement.getEName();
+                        if(eName.contains(designedElevation)){
+                            //设置标高
+                            map.put(designedElevation, wbsFormElement.getEKey());
+                        }else if(eName.contains(heightDeviation)){
+                            //高度偏差
+                            map.put(heightDeviation, wbsFormElement.getEKey());
+                        }else if(eName.contains(actualElevation)){
+                            //实际标高
+                            map.put(actualElevation, wbsFormElement.getEKey());
+                        }else if(eName.contains(difference)){
+                            //差值
+                            if(eName.contains("X")){
+                                map.put(difference + "X", wbsFormElement.getEKey());
+                            }else if (eName.contains("Y")){
+                                map.put(difference + "Y", wbsFormElement.getEKey());
+                            }
+                        }else if(eName.contains(deviation)){
+                            //偏差
+                            map.put(deviation, wbsFormElement.getEKey());
+                        }
+                    }
+
+                    //存储
+                    dataMaps.put(wbsTreeContract.getInitTableName(),map);
+                }
             }
 
             //构造表数据
             List<InsertDataVO> resultData = new LinkedList<>();
-            this.syncTabDataImpl(sgTabMaps, jlTabMaps, resultData);
+            //坐标
+            Map<String,Set<String>> coordinateMap = new HashMap<>();
+            this.syncTabDataImpl(sgTabMaps, jlTabMaps, resultData,coordinateMap);
 
             //入库处理
             if (resultData.size() > 0) {
@@ -1753,6 +1827,209 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
                                 filedNames.add(vo.getName());
                             }*/
                         }
+
+                        Map<String, String> stringStringMap = dataMaps.get(initTabName);
+
+                        //CL08水准测量记录表(监理)
+                        if(levelingTableName.equals(initTabName)){
+                            //设计标高
+                            String designedElevationNew = stringStringMap.get(designedElevation);
+                            HashMap<Integer, BigDecimal> designedElevationNewMap = new HashMap<>();
+                            //偏差
+                            String heightDeviationNew = stringStringMap.get(heightDeviation);
+                            HashMap<Integer, BigDecimal> heightDeviationNewMap = new HashMap<>();
+                            //实际标高
+                            String actualElevationNew = stringStringMap.get(actualElevation);
+
+                            //记录数量
+                            Integer rowMin = null;
+                            Integer rowMax = null;
+
+                            for (int i = 0; i < keys.size(); i++) {
+                                if(!Objects.equals(keys.get(i),designedElevationNew) && !Objects.equals(keys.get(i),heightDeviationNew)){
+                                    continue;
+                                }
+
+
+                                String value = values.get(i);
+                                //拆分数据
+                                String[] split1 = value.split("☆");
+
+                                List<String> heightDeviationList = new ArrayList<>();
+                                for (String s : split1) {
+                                    String[] split2 = s.split("_\\^_");
+                                    int rowNum = Integer.parseInt(split2[1].split("_")[0]);
+                                    //获取最大行数
+                                    if(rowMax == null){
+                                        rowMax = rowNum;
+                                    }else if(rowMax < rowNum){
+                                        rowMax = rowNum;
+                                    }
+                                    //获取最小行数
+                                    if(rowMin == null){
+                                        rowMin = rowNum;
+                                    }else if(rowMin > rowNum){
+                                        rowMin = rowNum;
+                                    }
+
+                                    if (keys.get(i).equals(designedElevationNew)) {
+                                        //设计标高
+                                        designedElevationNewMap.put(rowNum, new BigDecimal(split2[0]));
+                                    } else if (keys.get(i).equals(heightDeviationNew)){
+                                        if(StringUtils.isNotEmpty(split2[0])){
+                                            double v = Double.parseDouble(split2[0]);
+                                            //随机+ - 0/1
+                                            Random random = new Random();
+                                            int adjustment = random.nextInt(2);
+                                            if(v > 0){
+                                                v = v - adjustment;
+                                            }else{
+                                                v = v + adjustment;
+                                            }
+
+                                            //高度偏差
+                                            heightDeviationNewMap.put(rowNum, BigDecimal.valueOf(v));
+
+                                            heightDeviationList.add(new BigDecimal(v).setScale(0,RoundingMode.HALF_UP).intValue() + "_^_"+ split2[1]);
+                                        }
+                                    }
+                                }
+                                //设置偏高的值
+                                if(CollectionUtil.isNotEmpty(heightDeviationList)){
+                                    values.set(i, String.join("☆",heightDeviationList));
+                                }
+                            }
+                            if(rowMin==null){
+                                continue;
+                            }
+                            List<String> list = new ArrayList<>();
+
+                            //获取当前key对应的坐标
+                            Set<String> strings = coordinateMap.get(initTabName);
+                            String index = null;
+                            for (String string : strings) {
+                                String[] split = string.split("__");
+                                if(Objects.equals(split[0],actualElevationNew)){
+                                    index = split[1];
+                                }
+                            }
+                            //按照最小行数来计算
+                            for (int i = rowMin; i <= rowMax; i++) {
+                                BigDecimal designed = designedElevationNewMap.get(i);
+                                BigDecimal height = heightDeviationNewMap.get(i);
+
+                                if(designed == null || height == null){
+                                    continue;
+                                }
+                                BigDecimal v = designed.add(height.divide(new BigDecimal(1000)));
+                                //第5列,索引为4
+                                list.add(v.doubleValue() + "_^_"+ i + "_" + index);
+                            }
+                            //未获取到当前key的坐标就不设置值
+                            if(index == null){
+                                continue;
+                            }
+                            //设置实际标高的值
+                            values.set(keys.indexOf(actualElevationNew), String.join("☆",list));
+                        }else if(planePositionTableName.equals(initTabName)){
+                            //CL10平面位置检测记录表(监理)
+                            //差值
+                            String differenceNewX = stringStringMap.get(difference + "X");
+                            HashMap<Integer, BigDecimal> differenceNewXMap = new HashMap<>();
+
+                            String differenceNewY = stringStringMap.get(difference + "Y");
+                            HashMap<Integer, BigDecimal> differenceNewYMap = new HashMap<>();
+
+
+                            String deviationNew = stringStringMap.get(deviation);
+                            //记录数量
+                            Integer rowMin = null;
+                            Integer rowMax = null;
+                            for (int i = 0; i < keys.size(); i++) {
+                                if(!Objects.equals(keys.get(i),differenceNewX) && !Objects.equals(keys.get(i),differenceNewY)){
+                                    continue;
+                                }
+
+                                String value = values.get(i);
+                                //拆分数据
+                                String[] split1 = value.split("☆");
+
+                                List<String> list = new ArrayList<>();
+
+                                for (String s : split1) {
+                                    String[] split2 = s.split("_\\^_");
+                                    int rowNum = Integer.parseInt(split2[1].split("_")[0]);
+                                    //获取最大行数
+                                    if(rowMax == null){
+                                        rowMax = rowNum;
+                                    }else if(rowMax < rowNum){
+                                        rowMax = rowNum;
+                                    }
+                                    //获取最小行数
+                                    if(rowMin == null){
+                                        rowMin = rowNum;
+                                    }else if(rowMin > rowNum){
+                                        rowMin = rowNum;
+                                    }
+                                    if(StringUtils.isNotEmpty(split2[0])){
+                                        Integer v = Integer.parseInt(split2[0]);
+                                        //随机+ - 0/1
+                                        Random random = new Random();
+                                        int adjustment = random.nextInt(2);
+                                        if(v > 0){
+                                            v = v - adjustment;
+                                        }else{
+                                            v = v + adjustment;
+                                        }
+                                        if (keys.get(i).equals(differenceNewX)) {
+                                            //偏差X
+                                            differenceNewXMap.put(rowNum, new BigDecimal(split2[0]));
+                                        } else if (keys.get(i).equals(differenceNewY)){
+                                            //偏差Y
+                                            differenceNewYMap.put(rowNum, BigDecimal.valueOf(v));
+                                        }
+                                        list.add(v + "_^_"+ split2[1]);
+                                    }
+                                }
+                                values.set(i, String.join("☆",list));
+                            }
+                            if(rowMin==null){
+                                continue;
+                            }
+                            List<String> list = new ArrayList<>();
+                            //获取当前key对应的坐标
+                            Set<String> strings = coordinateMap.get(initTabName);
+                            String index = null;
+                            for (String string : strings) {
+                                String[] split = string.split("__");
+                                if(Objects.equals(split[0],deviationNew)){
+                                    index = split[1];
+                                }
+                            }
+                            //按照最小行数来计算
+                            for (int i = rowMin; i <= rowMax; i++) {
+                                BigDecimal x = differenceNewXMap.get(i);
+                                BigDecimal y = differenceNewYMap.get(i);
+
+                                if(x == null || y == null){
+                                    continue;
+                                }
+                                BigDecimal sqrt = sqrt(x.multiply(x).add(y.multiply(y)), 0);
+
+                                //第9列,索引为8
+                                list.add(sqrt.intValue() + "_^_"+ i + "_" + index);
+                            }
+                            //未获取到当前key的坐标就不设置值
+                            if(index == null){
+                                continue;
+                            }
+                            //设置实际标高的值
+                            values.set(keys.indexOf(deviationNew), String.join("☆",list));
+                        }
+
+
+
+
                         if (keys.size() > 0 && values.size() > 0 && keys.size() == values.size()) {
                             //alter SQL(扩容字段长度)
                             StringBuilder exStrBuilder = new StringBuilder();
@@ -1857,6 +2134,32 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
         }
     }
 
+    // 牛顿迭代法实现开平方
+    public  BigDecimal sqrt(BigDecimal value, int precision) {
+        // 检查负数输入
+        if (value.compareTo(BigDecimal.ZERO) < 0) {
+            throw new ArithmeticException("不能对负数开平方");
+        }
+
+        // 精度设置(保留小数位 + 额外2位保证精度)
+        MathContext mc = new MathContext(precision + 2, RoundingMode.HALF_EVEN);
+
+        // 初始值 = value / 2
+        BigDecimal guess = value.divide(BigDecimal.valueOf(2), mc);
+        BigDecimal lastGuess;
+
+        // 牛顿迭代公式:xₙ₊₁ = (xₙ + value/xₙ)/2
+        do {
+            lastGuess = guess;
+            guess = value.divide(guess, mc).add(guess)
+                    .divide(BigDecimal.valueOf(2), mc);
+        } while (guess.subtract(lastGuess).abs().compareTo(
+                BigDecimal.valueOf(1, precision + 1)) > 0); // 精度控制
+
+        // 返回最终结果(精确到指定小数位)
+        return guess.setScale(precision, RoundingMode.HALF_UP);
+    }
+
     /**
      * 构造数据
      *
@@ -1866,7 +2169,7 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
      * @throws Exception
      */
     private void syncTabDataImpl
-    (Map<String, String> sgData, Map<String, String> jlData, List<InsertDataVO> resultData) throws
+    (Map<String, String> sgData, Map<String, String> jlData, List<InsertDataVO> resultData, Map<String,Set<String>> coordinateMap) throws
             Exception {
         for (Map.Entry<String, String> sgTab : sgData.entrySet()) { //质检表
             String[] split = sgTab.getKey().split("---");
@@ -1885,6 +2188,22 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
                 //表的代码名称相同,就复制数据,例如:G10=G10
                 if (nodeNameRe.equals(nodeNameReJL)) {
                     String htmlStringJL = this.getHtmlString(pKeyIdJL);
+
+                    //解析html坐标,生成key与keyName的映射关系
+                    if (StringUtils.isNotEmpty(htmlStringJL)) {
+                        Document doc = Jsoup.parse(htmlStringJL);
+                        Elements select = doc.select("[id]");
+                        HashSet<String> strings = new HashSet<>();
+                        for (Element element : select) {
+                            String id = element.id();
+                            String[] split1 = id.split("__");
+                            String s = split1[1].split("_")[1];
+
+                            strings.add(split1[0] + "__" + s);
+                        }
+                        coordinateMap.put(initTabNameJL, strings);
+                    }
+
                     //获取质检实体表对应数据
                     List<Map<String, Object>> mapsList = jdbcTemplate.queryForList("select * from " + initTabName + " where p_key_id = " + pKeyId);
                     if (mapsList.size() > 0) {