|
@@ -1,5 +1,6 @@
|
|
|
package org.springblade.manager.service.impl;
|
|
|
|
|
|
+import cn.hutool.core.date.DateTime;
|
|
|
import cn.hutool.core.util.ObjectUtil;
|
|
|
import com.alibaba.fastjson.JSON;
|
|
|
import com.alibaba.fastjson.JSONArray;
|
|
@@ -12,6 +13,9 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|
|
import com.google.common.collect.Lists;
|
|
|
import lombok.AllArgsConstructor;
|
|
|
import org.apache.commons.lang.StringUtils;
|
|
|
+import org.apache.ibatis.session.ExecutorType;
|
|
|
+import org.apache.ibatis.session.SqlSession;
|
|
|
+import org.apache.ibatis.session.SqlSessionFactory;
|
|
|
import org.apache.poi.ss.usermodel.Cell;
|
|
|
import org.apache.poi.ss.usermodel.Row;
|
|
|
import org.apache.poi.ss.usermodel.Sheet;
|
|
@@ -107,6 +111,9 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
|
|
|
|
|
|
@Autowired
|
|
|
StringRedisTemplate redisTemplate;
|
|
|
+ @Autowired
|
|
|
+ private SqlSessionFactory sqlSessionFactory;
|
|
|
+
|
|
|
|
|
|
//存储当前合同段contractId对应的合同段树
|
|
|
private final Map<String, List<WbsTreeContractLazyVO>> localCacheNodes = new ConcurrentHashMap<>();
|
|
@@ -148,7 +155,7 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
|
|
|
throw new ServiceException("请勿重复提交,请60秒后再尝试");
|
|
|
}
|
|
|
bladeRedis.set("submit-wbs-contract:" + pawDTO.getContractId(), "1");
|
|
|
- bladeRedis.expire("submit-wbs-contract:" + pawDTO.getContractId(), 60);
|
|
|
+ bladeRedis.expire("submit-wbs-contract:" + pawDTO.getContractId(), 5);
|
|
|
|
|
|
String wbsTreeIds = pawDTO.getWbsTreeIds();
|
|
|
String[] ids = wbsTreeIds.split(",");
|
|
@@ -373,6 +380,9 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
|
|
|
constructionLedger.setContractId(Long.parseLong(pawDTO.getContractId()));
|
|
|
constructionLedgerList.add(constructionLedger);
|
|
|
}
|
|
|
+ //TODO 这里是添加公共节点id还是添加私有节点id还有待商榷 暂时先使用公共节点id完成逻辑
|
|
|
+ wbsTreeContract.setTreePId(wbsTreePrivate.getId());
|
|
|
+
|
|
|
return wbsTreeContract;
|
|
|
}
|
|
|
return null;
|
|
@@ -383,6 +393,31 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
|
|
|
//入库
|
|
|
this.insertBatch(wbsTreeContractList, 1000);
|
|
|
constructionLedgerFeign.initConstructionLedger(constructionLedgerList);
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ //绑定合同WBS树自身父级id 和 父级路径
|
|
|
+ baseMapper.updateContractPid(pawDTO.getProjectId(),pawDTO.getContractId());
|
|
|
+
|
|
|
+ // 获取所有节点数据并更新 ancestors 字段
|
|
|
+ try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false)) {
|
|
|
+ WbsTreeContractMapper mapper = sqlSession.getMapper(WbsTreeContractMapper.class);
|
|
|
+
|
|
|
+ // 获取所有节点数据
|
|
|
+ List<WbsTreeContract> list = repairWithMemoryCalculation(pawDTO.getProjectId(),pawDTO.getContractId());
|
|
|
+
|
|
|
+ int batchSize = 1000;
|
|
|
+ for (int i = 0; i < list.size(); i += batchSize) {
|
|
|
+ List<WbsTreeContract> batch = list.subList(i, Math.min(i + batchSize, list.size()));
|
|
|
+ mapper.updateBatchAncestorsByPKeyId(batch);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 统一提交事务
|
|
|
+ sqlSession.commit();
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new ServiceException("操作失败: " + e.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -3590,4 +3625,105 @@ public static boolean hasConflictingCodes(List<ImportTreeDto> list) {
|
|
|
setAncestors(child, parentChildMap, nodeMap);
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 使用内存计算优化修复合同数据
|
|
|
+ * 该方法加载指定项目和合同相关的所有节点到内存,并进行数据修复和优化
|
|
|
+ * 主要包括:构建节点映射、检测并修复循环引用、重新计算所有节点的祖先路径
|
|
|
+ *
|
|
|
+ * @param projectId 项目ID,用于筛选相关节点
|
|
|
+ * @param contractId 合同ID,用于进一步筛选相关节点
|
|
|
+ * @return 返回修复和优化后的节点列表
|
|
|
+ */
|
|
|
+ public List<WbsTreeContract> repairWithMemoryCalculation(String projectId,String contractId) {
|
|
|
+ // 1. 加载全量数据到内存
|
|
|
+ List<WbsTreeContract> allNodes = baseMapper.selectList(new QueryWrapper<WbsTreeContract>().lambda()
|
|
|
+ .eq(WbsTreeContract::getProjectId, projectId)
|
|
|
+ .eq(WbsTreeContract::getContractId,contractId));
|
|
|
+
|
|
|
+ // 构建节点映射,以便快速访问每个节点
|
|
|
+ Map<Long, WbsTreeContract> nodeMap = buildNodeMap(allNodes);
|
|
|
+
|
|
|
+ // 2. 检测并修复循环引用
|
|
|
+ // 这一步确保数据的一致性和完整性,避免后续处理中出现无限递归等问题
|
|
|
+ detectAndBreakCircles(allNodes, nodeMap);
|
|
|
+
|
|
|
+ // 3. 重新计算所有ancestors
|
|
|
+ // 由于节点关系可能发生变化(如修复循环引用),需要重新计算每个节点的祖先路径
|
|
|
+ allNodes.stream()
|
|
|
+ .forEach(node -> {
|
|
|
+ String correctAncestors = calculateCorrectAncestors(node, nodeMap);
|
|
|
+ node.setAncestorsPId(correctAncestors);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 返回修复和优化后的节点列表
|
|
|
+ return allNodes;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<Long, WbsTreeContract> buildNodeMap(List<WbsTreeContract> nodes) {
|
|
|
+ return nodes.stream().collect(Collectors.toMap(WbsTreeContract::getPKeyId, Function.identity()));
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private String calculateCorrectAncestors(WbsTreeContract node, Map<Long, WbsTreeContract> nodeMap) {
|
|
|
+ List<Long> path = new ArrayList<>();
|
|
|
+ WbsTreeContract current = node;
|
|
|
+ Set<Long> visited = new HashSet<>();
|
|
|
+
|
|
|
+ while (true) {
|
|
|
+ if (current == null || current.getPId() == null ||
|
|
|
+ current.getPId() == 0 || current.getPId().equals(current.getPKeyId())) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检测循环引用
|
|
|
+ if (visited.contains(current.getPId())) {
|
|
|
+ log.warn("Circular reference detected at node {}" + current.getPKeyId());
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ visited.add(current.getPKeyId());
|
|
|
+
|
|
|
+ current = nodeMap.get(current.getPId());
|
|
|
+ if (current != null) {
|
|
|
+ path.add(0, current.getPKeyId());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 安全限制
|
|
|
+ if (path.size() > 50) {
|
|
|
+ log.warn("Path too long (>50) at node {}" + node.getPKeyId());
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ path.add(0, 0L);
|
|
|
+ return String.join(",", path.stream().map(String::valueOf).toArray(String[]::new));
|
|
|
+ }
|
|
|
+
|
|
|
+ private void detectAndBreakCircles(List<WbsTreeContract> nodes, Map<Long, WbsTreeContract> nodeMap) {
|
|
|
+ nodes.forEach(node -> {
|
|
|
+ if (hasCircle(node, nodeMap)) {
|
|
|
+ log.warn("Breaking circle for node {}" + node.getPKeyId());
|
|
|
+ node.setParentId(null); // 断开循环引用
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean hasCircle(WbsTreeContract node, Map<Long, WbsTreeContract> nodeMap) {
|
|
|
+ Set<Long> visited = new HashSet<>();
|
|
|
+ WbsTreeContract current = node;
|
|
|
+
|
|
|
+ while (current != null && current.getPId() != null &&
|
|
|
+ !current.getPId().equals(current.getPKeyId())) {
|
|
|
+
|
|
|
+ if (visited.contains(current.getPId())) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ visited.add(current.getPKeyId());
|
|
|
+
|
|
|
+ current = nodeMap.get(current.getPId());
|
|
|
+ if (current == null) break;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
}
|