Explorar el Código

同步功能-正在写同步电签

LHB hace 2 meses
padre
commit
9864239dbb

+ 125 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/entity/WbsTreeSynchronousRecord.java

@@ -0,0 +1,125 @@
+package org.springblade.manager.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.util.Date;
+import lombok.Data;
+import org.springblade.core.mp.base.BaseEntity;
+
+/**
+ * WBS同步记录表
+ * @author LHB
+ * @TableName m_wbs_tree_synchronous_record
+ */
+@TableName(value ="m_wbs_tree_synchronous_record")
+@Data
+public class WbsTreeSynchronousRecord {
+    /**
+     * id
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 项目id
+     */
+    private Long projectId;
+
+    /**
+     * 项目名称
+     */
+    private String projectName;
+
+    /**
+     * 同步范围(1-从后管同步,2-同步到合同段)
+     */
+    @TableField("`range`")
+    private Integer range;
+    /**
+     * 同步范围名称
+     */
+    private String rangeName;
+    /**
+     * 合同段范围 逗号拼接的编号 101.未填报 102.已填报-未上报 103.未上报 104.待审批 105.已审批
+     */
+    private String contractRange;
+    /**
+     * 合同段范围名称
+     */
+    private String contractRangeName;
+
+    /**
+     * 同步源Id
+     */
+    private Long templateId;
+
+    /**
+     * 同步源名称
+     */
+    private String templateName;
+
+    /**
+     *  同步类型 逗号拼接的编号 1.新增表单 2.清表配置 3.元素配置 4.电签配置 5.公式配置 6.默认值配置 7.表单排序
+     */
+    private String type;
+
+    /**
+     *  同步类型名称
+     */
+    private String typeName;
+
+    /**
+     * 同步节点id 多个节点
+     */
+    private String nodeId;
+
+    /**
+     * 同步节点名称
+     */
+    private String nodeName;
+    /**
+     * 表单Ids
+     */
+    private String formIds;
+
+    /**
+     * 是否删除(0-未删除,1-删除)
+     */
+    private Integer isDeleted;
+
+    /**
+     * 状态(0-未同步,1-正在同步,2-已同步,3-同步失败)
+     */
+    private Integer status;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+    /**
+     * 创建人
+     */
+    private String createUser;
+
+    /**
+     * 创建人id
+     */
+    private Long createUserId;
+
+    /**
+     * 修改时间
+     */
+    private Date updateTime;
+
+    /**
+     * 修改人
+     */
+    private String updateUser;
+
+    /**
+     * 修改人id
+     */
+    private Long updateUserId;
+}

+ 55 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/enums/WbsSyncTypeEnum.java

@@ -0,0 +1,55 @@
+package org.springblade.manager.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springblade.core.tool.utils.ObjectUtil;
+
+/**
+ * @author LHB
+ */
+@Getter
+@AllArgsConstructor
+public enum WbsSyncTypeEnum {
+    UNKNOWN(-1, ""),
+    INSERT_FORM(1, "新增表单"),
+    CLEAR_TABLE_CONFIG(2, "清表配置"),
+    ELEMENT_CONFIG(3, "元素配置"),
+    E_VISA_CONFIG(4, "电签配置"),
+    FORMULA_CONFIG(5, "公式配置"),
+    DEFAULT_VALUE_CONFIG(6, "默认值配置"),
+    FORM_SORT(7, "表单排序"),
+    /**
+     * 合同段同步范围
+     */
+    NOT_FILLED_IN(101, "未填报"),
+    ALREADY_FILLED_IN_NOT_REPORTED(102, "已填报-未上报"),
+    NOT_REPORTED(103, "未上报"),
+    PENDING_APPROVAL(104, "待审批"),
+    APPROVED(105, "已审批"),
+    ;
+
+    /**
+     * 编码
+     */
+    private Integer code;
+
+    /**
+     * 描述
+     */
+    private String desc;
+
+    /**
+     * 根据编码获取枚举描述
+     */
+    public static String getByCode(int code) {
+        // 判断code
+        if(ObjectUtil.isNotEmpty(code)){
+            for (StorageTypeEnum storageTypeEnum : StorageTypeEnum.values()) {
+                if (storageTypeEnum.getCode() == code) {
+                    return storageTypeEnum.getDesc();
+                }
+            }
+        }
+        return WbsSyncTypeEnum.UNKNOWN.getDesc();
+    }
+}

+ 77 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/controller/WbsTreeSynchronousRecordController.java

@@ -0,0 +1,77 @@
+package org.springblade.manager.controller;
+
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.manager.entity.WbsTreeSynchronousRecord;
+import org.springblade.manager.service.WbsTreeSynchronousRecordService;
+import org.springblade.core.mp.support.Condition;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.tool.api.R;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.io.Serializable;
+
+/**
+ * WBS同步记录表(MWbsTreeSynchronousRecord)表控制层
+ *
+ * @author makejava
+ * @since 2025-05-15 13:50:25
+ */
+@RestController
+@RequestMapping("/synchronousRecord")
+public class WbsTreeSynchronousRecordController {
+    /**
+     * 服务对象
+     */
+    @Resource
+    private WbsTreeSynchronousRecordService mWbsTreeSynchronousRecordService;
+
+    /**
+     * 分页查询所有数据
+     *
+     * @param record 查询对象
+     * @param query  分页对象
+     * @return 所有数据
+     */
+    @GetMapping("/page")
+    public R<IPage<WbsTreeSynchronousRecord>> selectAll(WbsTreeSynchronousRecord record, Query query) {
+        LambdaQueryWrapper<WbsTreeSynchronousRecord> lambda = new QueryWrapper().lambda();
+        lambda.eq(record.getProjectId() != null, WbsTreeSynchronousRecord::getProjectId, record.getProjectId())
+                .eq(record.getRange() != null, WbsTreeSynchronousRecord::getRange, record.getRange());
+        if (!StringUtil.isBlank(record.getType())) {
+            lambda.apply("FIND_IN_SET({0},type)", record.getType());
+        }
+        IPage<WbsTreeSynchronousRecord> page = mWbsTreeSynchronousRecordService.page(Condition.getPage(query), lambda);
+        return R.data(page);
+    }
+
+    /**
+     * 通过主键查询单条数据
+     *
+     * @param id 主键
+     * @return 单条数据
+     */
+    @GetMapping("getById")
+    public R<WbsTreeSynchronousRecord> selectOne(@RequestParam Long id) {
+        return R.data(this.mWbsTreeSynchronousRecordService.getById(id));
+    }
+
+
+    /**
+     * 新增数据
+     *
+     * @param mWbsTreeSynchronousRecord 实体对象
+     * @return 新增结果
+     */
+    @PostMapping("add")
+    public R<Integer> insert(@RequestBody WbsTreeSynchronousRecord mWbsTreeSynchronousRecord) {
+        return R.data(this.mWbsTreeSynchronousRecordService.insert(mWbsTreeSynchronousRecord));
+    }
+
+
+}
+

+ 21 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/WbsTreeSynchronousRecordMapper.java

@@ -0,0 +1,21 @@
+package org.springblade.manager.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Param;
+import org.springblade.manager.entity.WbsTreeSynchronousRecord;
+
+/**
+* @author LHB
+* @description 针对表【m_wbs_tree_synchronous_record(WBS同步记录表)】的数据库操作Mapper
+* @createDate 2025-05-15 13:52:09
+* @Entity generator.domain.MWbsTreeSynchronousRecord
+*/
+public interface WbsTreeSynchronousRecordMapper extends BaseMapper<WbsTreeSynchronousRecord> {
+
+}
+
+
+
+

+ 30 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/WbsTreeSynchronousRecordMapper.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.springblade.manager.mapper.WbsTreeSynchronousRecordMapper">
+
+    <resultMap id="BaseResultMap" type="org.springblade.manager.entity.WbsTreeSynchronousRecord">
+            <id property="id" column="id" />
+            <result property="projectId" column="project_id" />
+            <result property="projectName" column="project_name" />
+            <result property="range" column="range" />
+            <result property="source" column="source" />
+            <result property="type" column="type" />
+            <result property="nodeId" column="node_id" />
+            <result property="nodeName" column="node_name" />
+            <result property="isDeleted" column="is_deleted" />
+            <result property="createTime" column="create_time" />
+            <result property="createUser" column="create_user" />
+            <result property="createUserId" column="create_user_id" />
+            <result property="updateTime" column="update_time" />
+            <result property="updateUser" column="update_user" />
+            <result property="updateUserId" column="update_user_id" />
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id,project_id,project_name,range,source,type,
+        node_id,node_name,is_deleted,create_time,create_user,
+        create_user_id,update_time,update_user,update_user_id
+    </sql>
+</mapper>

+ 17 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/WbsTreeSynchronousRecordService.java

@@ -0,0 +1,17 @@
+package org.springblade.manager.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.springblade.core.mp.support.Query;
+import org.springblade.manager.entity.WbsTreeSynchronousRecord;
+
+/**
+* @author LHB
+* @description 针对表【m_wbs_tree_synchronous_record(WBS同步记录表)】的数据库操作Service
+* @createDate 2025-05-15 13:52:09
+*/
+public interface WbsTreeSynchronousRecordService extends IService<WbsTreeSynchronousRecord> {
+
+    Integer insert(WbsTreeSynchronousRecord mWbsTreeSynchronousRecord);
+
+}

+ 1017 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsSynchronousServiceImpl.java

@@ -0,0 +1,1017 @@
+package org.springblade.manager.service.impl;
+
+import cn.hutool.core.date.DateTime;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.google.common.collect.Lists;
+import org.apache.commons.lang.ObjectUtils;
+import org.springblade.common.constant.CommonConstant;
+import org.springblade.common.utils.SnowFlakeUtil;
+import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.tool.utils.*;
+import org.springblade.manager.entity.*;
+import org.springblade.manager.enums.WbsSyncTypeEnum;
+import org.springblade.manager.mapper.*;
+import org.springblade.system.cache.ParamCache;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.io.File;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * Wbs 同步功能Service
+ *
+ * @author LHB
+ */
+@Service
+public class WbsSynchronousServiceImpl {
+
+    //电签
+    @Autowired
+    private TextdictInfoMapper textdictInfoMapper;
+    //合同wbs
+    @Autowired
+    private WbsTreeContractMapper wbsTreeContractMapper;
+    //合同
+    @Autowired
+    private ContractInfoMapper contractInfoMapper;
+    //项目wbs
+    @Autowired
+    private WbsTreePrivateMapper wbsTreePrivateMapper;
+    //系统wbs
+    @Autowired
+    private WbsTreeMapper wbsTreeMapper;
+    //项目
+    @Autowired
+    private ProjectInfoMapper projectInfoMapper;
+    //同步信息
+    @Autowired
+    private WbsTreeSynchronousRecordMapper synchronousRecordMapper;
+
+
+    @Async("taskExecutor1")
+    public void syncExecute(WbsTreeSynchronousRecord wbsTreeSynchronousRecord) {
+        // 类型枚举 WbsSyncTypeEnum.ALREADY_FILLED_IN_NOT_REPORTED
+        //范围 1.项目,2.合同
+        Integer range = wbsTreeSynchronousRecord.getRange();
+        if (range == 1) {
+            insertPrivateNode(wbsTreeSynchronousRecord);
+        } else if (range == 2) {
+            insertContractNode(wbsTreeSynchronousRecord);
+        } else {
+            throw new ServiceException("同步类型错误");
+        }
+    }
+
+    /**
+     * 项目同步 获取源节点数据和当前节点数据
+     *
+     * @param wbsTreeSynchronousRecord
+     */
+    public void insertPrivateNode(WbsTreeSynchronousRecord wbsTreeSynchronousRecord) {
+        //同步类型 1.新增表单,2.新增节点,3.删除节点,4.删除表单,5.修改节点,6.修改表单,7.修改节点表单
+        String type = wbsTreeSynchronousRecord.getType();
+        List<Integer> collect = Arrays.stream(type.split(",")).map(Integer::parseInt).collect(Collectors.toList());
+
+        // 选中节点
+        String nodeId = wbsTreeSynchronousRecord.getNodeId();
+        String[] nodeIds = nodeId.split(",");
+
+        //选中表单
+        String formIds = wbsTreeSynchronousRecord.getFormIds();
+        List<Long> formList;
+        if (StringUtil.isNotBlank(formIds)) {
+            formList = Arrays.stream(formIds.split(",")).map(Long::parseLong).collect(Collectors.toList());
+        } else {
+            formList = null;
+        }
+        for (String primaryKeyId : nodeIds) {
+            if (StringUtils.isNotEmpty(primaryKeyId)) {
+                //获取当前节点对应节点信息
+                WbsTreePrivate wbsTreePrivate = wbsTreePrivateMapper.selectOne(Wrappers.<WbsTreePrivate>lambdaQuery().eq(WbsTreePrivate::getPKeyId, primaryKeyId));
+                //获取项目信息
+                ProjectInfo projectInfo = projectInfoMapper.selectOne(Wrappers.<ProjectInfo>lambdaQuery().eq(ProjectInfo::getId, wbsTreePrivate.getProjectId()));
+                //当前项目模板对应的节点信息
+                List<WbsTreePrivate> templateNodes = new ArrayList<>();
+                //质检
+                //当前节点引用的模板为公有模板
+
+                if (("public").equals(projectInfo.getReferenceWbsTemplateType())) {
+                    //当前节点对应的模板节点信息
+                    WbsTree wbsTreeTemplate = wbsTreeMapper.selectOne(Wrappers.<WbsTree>lambdaQuery()
+                            .eq(WbsTree::getId, wbsTreePrivate.getTreePId()));
+                    //获取模板中当前节点的子节点的数据------------------------------------------------------------------------------------------------------------------------
+                    LambdaQueryWrapper<WbsTree> wrapperTree = Wrappers.lambdaQuery();
+                    wrapperTree.eq(WbsTree::getWbsId, wbsTreePrivate.getWbsId())
+                            .eq(WbsTree::getIsDeleted, 0);
+                    if (wbsTreeTemplate.getParentId() != 0) {
+                        wrapperTree.apply("FIND_IN_SET({0},ancestors)", wbsTreeTemplate.getId());
+                    }
+                    //模板节点的所有子节点信息
+                    List<WbsTree> wbsTrees = wbsTreeMapper.selectList(wrapperTree);
+                    //3、获取需要新增的节点或者表单节点信息 以及他们对应的父级节点信息--------------------------------------------------------------------------------------------
+                    if (CollectionUtil.isEmpty(wbsTrees)) {
+                        System.out.println("模板节点未找到");
+                        continue;
+                    }
+                    templateNodes = BeanUtil.copyProperties(wbsTrees, WbsTreePrivate.class);
+                    templateNodes.forEach(f -> f.setTreePId(f.getId()));
+
+                } else if (("private").equals(projectInfo.getReferenceWbsTemplateType())) {
+                    //根据wbsTreePrivate的wbsId=私有引用的pKeyId来获取引用树根节点
+                    WbsTreePrivate wbsTreePrivateRoot = wbsTreePrivateMapper.selectOne(Wrappers.<WbsTreePrivate>lambdaQuery()
+                            .eq(WbsTreePrivate::getPKeyId, wbsTreePrivate.getWbsId()));
+
+                    //获取当前选中节点与私有模板对应的节点信息 父级模板项目的当前选中节点
+                    wbsTreePrivateRoot = wbsTreePrivateMapper.selectOne(Wrappers.<WbsTreePrivate>lambdaQuery()
+                            .eq(WbsTreePrivate::getProjectId, wbsTreePrivateRoot.getProjectId())
+                            .eq(WbsTreePrivate::getTreePId, wbsTreePrivate.getTreePId()));
+
+                    //获取模板中当前节点的子节点的数据------------------------------------------------------------------------------------------------------------------------
+                    LambdaQueryWrapper<WbsTreePrivate> wrapperPrivate = Wrappers.lambdaQuery();
+                    wrapperPrivate.eq(WbsTreePrivate::getProjectId, wbsTreePrivateRoot.getProjectId())
+                            .eq(WbsTreePrivate::getIsDeleted, 0);
+                    //判断如果为顶级顶级节点
+                    if (wbsTreePrivate.getTreePId() != 0) {
+                        wrapperPrivate.apply("FIND_IN_SET({0},ancestors_p_id)", wbsTreePrivateRoot.getPId());
+                    }
+                    templateNodes = wbsTreePrivateMapper.selectList(wrapperPrivate);
+                }
+
+                if (CollectionUtil.isEmpty(templateNodes)) {
+                    System.out.println("未找到模板对应节点");
+                    continue;
+                }
+
+
+                //2、获取当前节点的子节点的数据------------------------------------------------------------------------------------------------------------------------
+                LambdaQueryWrapper<WbsTreePrivate> wrapperPrivate = Wrappers.lambdaQuery();
+                wrapperPrivate.eq(WbsTreePrivate::getWbsId, wbsTreePrivate.getWbsId()).eq(WbsTreePrivate::getIsDeleted, 0);
+                //判断如果为顶级顶级节点
+                if (wbsTreePrivate.getTreePId() != 0) {
+                    wrapperPrivate.apply("FIND_IN_SET({0},ancestors_p_id)", wbsTreePrivate.getPId());
+                }
+
+                //当前表节点下的节点信息
+                List<WbsTreePrivate> wbsTreePrivates = wbsTreePrivateMapper.selectList(wrapperPrivate);
+
+                if (wbsTreePrivates.isEmpty()) {
+                    System.out.println("未找到模板对应节点");
+                    continue;
+                }
+
+                //获取id 和 tree_p_id 组成的集合
+                Set<Long> ids = wbsTreePrivates.stream().map(WbsTreePrivate::getTreePId).collect(Collectors.toSet());
+                //3.1筛选出需要新增的节点
+                List<WbsTreePrivate> addPrivateNodes = templateNodes.stream()
+                        .filter(f -> !ids.contains(f.getTreePId()))
+                        .collect(Collectors.toList());
+
+                //新增数据二次筛选  只保留任务选中的表单   但可能新增的数据包含新节点
+                if (CollectionUtil.isNotEmpty(formList)) {
+                    addPrivateNodes = addPrivateNodes.stream().filter(f -> f.getType() == 1 || formList.contains(f.getPKeyId())).collect(Collectors.toList());
+                }
+
+                //筛选出需要更新的节点  同时做数据隔离
+                List<WbsTreePrivate> editPrivateNodes = new ArrayList<>();
+                for (WbsTreePrivate templateNode : templateNodes) {
+                    //更新只跟新表单
+                    if (templateNode.getType() == 2) {
+                        for (WbsTreePrivate editPrivateNode : wbsTreePrivates) {
+                            // 判断模板表与项目表 html是否一致
+                            if (ObjectUtils.equals(templateNode.getTreePId(), editPrivateNode.getTreePId())) {
+                                if (editPrivateNode.getExcelId() == null
+                                        || editPrivateNode.getHtmlUrl() == null
+                                        || ObjectUtils.equals(templateNode.getExcelId(), editPrivateNode.getExcelId())
+                                        || ObjectUtils.equals(templateNode.getHtmlUrl(), editPrivateNode.getHtmlUrl())) {
+
+                                    //清表
+                                    if (collect.contains(2)) {
+                                        editPrivateNode.setExcelId(templateNode.getExcelId());
+                                        editPrivateNode.setInitTableName(templateNode.getInitTableName());
+                                        editPrivateNode.setHtmlUrl(templateNode.getHtmlUrl());
+                                        if (templateNode.getExcelId() != null) {
+                                            editPrivateNode.setIsLinkTable(2);
+                                        }
+
+                                    }
+                                    //元素 是与表绑定的
+                                    if (collect.contains(3)) {
+                                        editPrivateNode.setInitTableName(templateNode.getInitTableName());
+                                    }
+
+
+                                    //排序
+                                    if (collect.contains(7)) {
+                                        editPrivateNode.setSort(templateNode.getSort());
+                                    }
+
+                                    editPrivateNodes.add(editPrivateNode);
+                                }
+                            }
+                        }
+                    }
+                }
+                //修改数据二次筛选  只保留任务选中的表单   但可能新增的数据包含新节点
+                if (CollectionUtil.isNotEmpty(formList)) {
+                    editPrivateNodes = editPrivateNodes.stream().filter(f -> formList.contains(f.getPKeyId())).collect(Collectors.toList());
+                }
+
+
+
+                Collections.sort(collect);
+                for (Integer i : collect) {
+                    switch (i) {
+                        //添加表单
+                        case 1:
+                            insertPrivateForm(wbsTreePrivates, addPrivateNodes);
+                            //如果同时选择新增表单和其他的同步类型  在操作其他类型的时候需要添加新的表单
+                            if (CollectionUtil.isNotEmpty(addPrivateNodes)) {
+                                editPrivateNodes.addAll(addPrivateNodes.stream().filter(f -> f.getType() == 2).collect(Collectors.toList()));
+                            }
+                            break;
+                        //清表
+                        case 2:
+                            //元素
+                        case 3:
+                            //排序
+                        case 7:
+                            //元素配置
+                            for (WbsTreePrivate editPrivateNode : editPrivateNodes) {
+                                wbsTreePrivateMapper.updateById(editPrivateNode);
+                            }
+                            break;
+                        //电签 如果有数据 与节点绑定
+                        case 4:
+                            //默认值  如果有数据 与节点绑定
+                        case 6:
+
+                            updateEViSa(collect, wbsTreeSynchronousRecord.getTemplateId(), projectInfo.getId(),templateNodes, editPrivateNodes);
+
+
+                            break;
+
+
+                        //公式配置
+                        case 5:
+                            break;
+
+
+                        default:
+                            break;
+                    }
+                }
+            }
+        }
+        wbsTreeSynchronousRecord.setStatus(2);
+        synchronousRecordMapper.updateById(wbsTreeSynchronousRecord);
+    }
+
+
+    /**
+     * 合同段同步 获取源节点数据和当前节点数据
+     */
+    public void insertContractNode(WbsTreeSynchronousRecord wbsTreeSynchronousRecord) {
+        //同步类型 1.新增表单,2.新增节点,3.删除节点,4.删除表单,5.修改节点,6.修改表单,7.修改节点表单
+        String type = wbsTreeSynchronousRecord.getType();
+        List<Integer> collect = Arrays.stream(type.split(",")).map(Integer::parseInt).collect(Collectors.toList());
+
+        // 选中节点
+        String nodeId = wbsTreeSynchronousRecord.getNodeId();
+        if (StringUtil.isBlank(nodeId)) {
+            return;
+        }
+        String[] nodeIds = nodeId.split(",");
+        //选中表单
+        String formIds = wbsTreeSynchronousRecord.getFormIds();
+        List<Long> formList;
+        if (StringUtil.isNotBlank(formIds)) {
+            formList = Arrays.stream(formIds.split(",")).map(Long::parseLong).collect(Collectors.toList());
+        } else {
+            formList = null;
+        }
+
+        //获取当前项目所有合同---------------------------------------------------------------------------------------------------
+        List<ContractInfo> contractInfos = contractInfoMapper.selectContractIdByProjectId(String.valueOf(wbsTreeSynchronousRecord.getProjectId()));
+
+
+        for (String primaryKeyId : nodeIds) {
+
+            //获取当前节点对应节点信息
+            WbsTreePrivate wbsTreePrivate = wbsTreePrivateMapper.selectOne(Wrappers.<WbsTreePrivate>lambdaQuery()
+                    .eq(WbsTreePrivate::getPKeyId, primaryKeyId));
+
+            //当前项目选中的节点 获取当前节点的所有子节点数据
+            //1、获取当前节点的子节点 表单的数据------------------------------------------------------------------------------------------------------------------------
+            LambdaQueryWrapper<WbsTreePrivate> wrapperPrivate = Wrappers.lambdaQuery();
+            wrapperPrivate
+                    .eq(WbsTreePrivate::getWbsId, wbsTreePrivate.getWbsId())
+                    .eq(WbsTreePrivate::getIsDeleted, 0);
+            //判断如果为顶级顶级节点
+            if (wbsTreePrivate.getTreePId() != 0) {
+                wrapperPrivate.apply("FIND_IN_SET({0},ancestors_p_id)", wbsTreePrivate.getPId());
+            }
+            //当前项目的子节点数据
+            List<WbsTreePrivate> wbsTreePrivates = wbsTreePrivateMapper.selectList(wrapperPrivate);
+
+            if (wbsTreePrivates.isEmpty()) {
+                System.out.println("无法找到模板对应节点,请检查模板节点");
+                continue;
+            }
+
+            //合同同步
+            for (ContractInfo contractInfo : contractInfos) {
+                //获取合同下当前节点的数据
+                WbsTreeContract wbsTreeContract = wbsTreeContractMapper.selectOne(Wrappers.<WbsTreeContract>lambdaQuery()
+                        .eq(WbsTreeContract::getContractId, contractInfo.getId())
+                        .eq(WbsTreeContract::getIsTypePrivatePid, primaryKeyId));
+                //如果没有查询到,表示该合同下不存在该节点
+                if (wbsTreeContract == null) {
+                    //不能抛异常  不然就会中止程序  开发阶段先抛异常,后续统一处理
+                    System.out.println("当前节点不存在");
+                    continue;
+                }
+
+
+                //获取合同 当前节点的所有子节点数据
+                LambdaQueryWrapper<WbsTreeContract> wrapperContract = Wrappers.lambdaQuery();
+                wrapperContract
+                        .eq(WbsTreeContract::getContractId, contractInfo.getId())
+                        .eq(WbsTreeContract::getIsDeleted, 0);
+                //判断如果为顶级顶级节点
+                if (wbsTreeContract.getTreePId() != 0) {
+                    wrapperPrivate.apply("FIND_IN_SET({0},ancestors_p_id)", wbsTreeContract.getPId());
+                }
+
+                //当前合同的子节点数据
+                List<WbsTreeContract> wbsTreeContracts = wbsTreeContractMapper.selectList(wrapperContract);
+
+                //合同段节点对应的项目id
+                List<Long> typePrivateIds = wbsTreeContracts.stream().map(WbsTreeContract::getIsTypePrivatePid).collect(Collectors.toList());
+
+                //需要新增的表单
+                List<WbsTreePrivate> addPrivateNodes = wbsTreePrivates.stream().filter(f -> !typePrivateIds.contains(f.getPKeyId())).collect(Collectors.toList());
+
+                //如果没有选中 则默认同步所有
+                if (CollectionUtil.isNotEmpty(formList)) {
+                    //二次筛选  只保留任务选中的表单   但可能新增的数据包含新节点
+                    List<Long> finalFormList = formList;
+                    addPrivateNodes = addPrivateNodes.stream().filter(f -> f.getType() == 1 || finalFormList.contains(f.getPKeyId())).collect(Collectors.toList());
+                }
+
+                //筛选出需要更新的节点  同时做数据隔离
+                List<WbsTreeContract> editContractNodes = new ArrayList<>();
+                for (WbsTreePrivate templateNode : wbsTreePrivates) {
+                    //更新只跟新表单
+                    if (templateNode.getType() == 2) {
+                        for (WbsTreeContract editContractNode : wbsTreeContracts) {
+                            // 判断模板表与项目表 html是否一致
+                            if (ObjectUtils.equals(templateNode.getId(), editContractNode.getId())) {
+                                if (editContractNode.getExcelId() == null
+                                        || editContractNode.getHtmlUrl() == null
+                                        || ObjectUtils.equals(templateNode.getExcelId(), editContractNode.getExcelId())
+                                        || ObjectUtils.equals(templateNode.getHtmlUrl(), editContractNode.getHtmlUrl())) {
+
+                                    //清表
+                                    if (collect.contains(2)) {
+                                        editContractNode.setExcelId(templateNode.getExcelId());
+                                        editContractNode.setInitTableName(templateNode.getInitTableName());
+                                        editContractNode.setHtmlUrl(templateNode.getHtmlUrl());
+                                        if (templateNode.getExcelId() != null) {
+                                            editContractNode.setIsLinkTable(2);
+                                        }
+
+                                    }
+                                    //公式
+                                    if (collect.contains(3)) {
+                                        editContractNode.setInitTableName(templateNode.getInitTableName());
+                                    }
+
+
+                                    //排序
+                                    if (collect.contains(7)) {
+                                        editContractNode.setSort(templateNode.getSort());
+                                    }
+
+                                    editContractNodes.add(editContractNode);
+                                }
+                            }
+                        }
+                    }
+                }
+                //修改数据二次筛选  只保留任务选中的表单   但可能新增的数据包含新节点
+                if (CollectionUtil.isNotEmpty(formList)) {
+                    editContractNodes = editContractNodes.stream().filter(f -> formList.contains(f.getPKeyId())).collect(Collectors.toList());
+                }
+
+                //合同段新增节点
+                List<WbsTreeContract> addContractNode = null;
+                if (addPrivateNodes != null) {
+                    addContractNode = BeanUtil.copyProperties(addPrivateNodes, WbsTreeContract.class);
+                }
+
+
+                Collections.sort(collect);
+                for (Integer i : collect) {
+                    switch (i) {
+                        //添加表单
+                        case 1:
+                            insertContractForm(contractInfo, wbsTreePrivates, addContractNode);
+                            //如果同时选择新增表单和其他的同步类型  在操作其他类型的时候需要添加新的表单
+                            if (CollectionUtil.isNotEmpty(addContractNode)) {
+                                editContractNodes.addAll(addContractNode.stream().filter(f -> f.getType() == 2).collect(Collectors.toList()));
+                            }
+                            break;
+                        //更新
+                        case 2:
+                            if (CollectionUtil.isNotEmpty(editContractNodes)) {
+                                for (WbsTreeContract editContractNode : editContractNodes) {
+                                    wbsTreeContractMapper.updateById(editContractNode);
+                                }
+                            }
+                            break;
+                        case 3:
+                            break;
+                        case 5:
+                            break;
+                        //排序
+                        case 7:
+
+                            if (CollectionUtil.isNotEmpty(editContractNodes)) {
+                                for (WbsTreeContract editContractNode : editContractNodes) {
+                                    wbsTreeContractMapper.updateById(editContractNode);
+                                }
+                            }
+
+                            break;
+                        default:
+                            break;
+                    }
+                }
+
+            }
+        }
+    }
+
+
+    /**
+     * 项目
+     * 类型 1 添加表单
+     *
+     * @param wbsTreePrivates 当前项目对应节点的子节点
+     * @param addPrivateNodes 需要新增的节点
+     */
+    public void insertPrivateForm(List<WbsTreePrivate> wbsTreePrivates, List<WbsTreePrivate> addPrivateNodes) {
+        List<WbsTreePrivate> addData = new ArrayList<>();
+
+        //------------------------------------------------新增-------------------------------------------------------------------------
+        if (CollectionUtil.isNotEmpty(addPrivateNodes)) {
+            //先给每个新增节点赋唯一id,项目id,父级id
+            addPrivateNodes.forEach(f -> f.setPKeyId(SnowFlakeUtil.getId()));
+
+            //给每一个节点的父级id
+            for (WbsTreePrivate addPrivateNode : addPrivateNodes) {
+                if (addPrivateNode.getParentId() == 0) {
+                    continue;
+                }
+
+                //查询出当前模板节点的父节点 去获取对应项目节点  如果父节点为0就跳过
+                List<WbsTreePrivate> addPrivateParentNodes = wbsTreePrivates.stream().filter(f -> f.getTreePId().equals(addPrivateNode.getParentId())).collect(Collectors.toList());
+                //如果没有数据  就表示这条数据的父节点也时新增节点 就需要从新增节点集合中找父级节点
+                if (addPrivateParentNodes.isEmpty()) {
+                    addPrivateParentNodes = addPrivateNodes.stream().filter(f -> f.getId().equals(addPrivateNode.getParentId())).collect(Collectors.toList());
+                }
+                //如果现在还找不到当前节点的父节点就表示数据有问题
+                if (addPrivateParentNodes.isEmpty()) {
+                    //TODO
+                    continue;
+                }
+                //当前新增节点的父节点
+                WbsTreePrivate parent = addPrivateParentNodes.get(0);
+
+                addPrivateNode.setPId(parent.getPKeyId());
+                addPrivateNode.setWbsId(parent.getWbsId());
+                addPrivateNode.setWbsType(parent.getWbsType());
+                addPrivateNode.setIsAddConceal(0);
+                addPrivateNode.setProjectId(parent.getProjectId());
+                //TODO 后续如果把p_key_id改成了id做 唯一id
+                addPrivateNode.setTreePId(addPrivateNode.getId());
+                //后续如果使用id做当前表主键 则先在赋值treePid之后再去赋值id    addPrivateNode.setId(SnowFlakeUtil.getId());
+                //更新创建时间
+                addPrivateNode.setCreateTime(DateTime.now());
+            }
+            addData.addAll(addPrivateNodes);
+        }
+        //设置html_url----------------------------------------------------------------------------------------------------------
+        //如果有新增的数据 旧设置html_url
+        if (CollectionUtil.isNotEmpty(addData)) {
+            try {
+                String file_path = ParamCache.getValue(CommonConstant.SYS_LOCAL_URL);
+                Set<String> urls = new HashSet<>();
+                for (WbsTreePrivate tree : addData) {
+                    if (org.apache.commons.lang3.StringUtils.isNotBlank(tree.getHtmlUrl())) {
+                        String[] split = tree.getHtmlUrl().split("/");
+                        String htmlUrl = file_path + "privateUrlCopy/" + tree.getPdfUrl() + "/" + split[split.length - 1];
+                        if (!urls.contains(tree.getHtmlUrl())) {
+                            urls.add(tree.getHtmlUrl());
+                            File file_in = ResourceUtil.getFile(tree.getHtmlUrl());
+                            if (!file_in.exists() || file_in.length() == 0) {
+                                continue;
+                            }
+                            File file_out = ResourceUtil.getFile(htmlUrl);
+                            FileUtil.copy(file_in, file_out);
+                        }
+                        tree.setHtmlUrl(htmlUrl);
+                    }
+                }
+            } catch (Exception e) {
+                System.out.println("重置表单路径错误" + e.getMessage());
+            }
+            //查询出当前项目所有节点---------------------------------------------------------------------------------------------------
+            List<WbsTreePrivate> addList = wbsTreePrivateMapper.selectList(Wrappers.<WbsTreePrivate>lambdaQuery()
+                    .select(WbsTreePrivate::getPKeyId, WbsTreePrivate::getPId)
+                    .eq(WbsTreePrivate::getIsDeleted, 0)
+                    .eq(WbsTreePrivate::getProjectId, addData.get(0).getProjectId()));
+
+            addList.addAll(addData);
+            //组合祖级路径 根据当前选中节点为开始
+            Map<Long, WbsTreePrivate> collect = addList.stream().collect(Collectors.toMap(WbsTreePrivate::getPKeyId, Function.identity()));
+
+            addData.forEach(node -> {
+                String correctAncestors = createAncestorsPId(node, collect);
+                node.setAncestorsPId(correctAncestors);
+            });
+
+            //新增-----------------------------------------------------------------------------------------------------------------
+            List<List<WbsTreePrivate>> partition = Lists.partition(addData, 1000);
+            Integer count = 0;
+            for (List<WbsTreePrivate> data : partition) {
+                //单个批次一个事务,只会回滚当前批次数据
+                Integer i = wbsTreePrivateMapper.insertBatchSomeColumn(data);
+                //如果失败  -- - - - - 继续执行   或者把当前节点的p_key_id 记录到某个地方 方便后续处理
+                if (i == 0) {
+                    List<Long> collect1 = addData.stream().map(WbsTreePrivate::getPKeyId).collect(Collectors.toList());
+                    //这里可以保存到数据库指定错误日志表
+
+                }
+
+                count += i;
+            }
+        }
+    }
+
+    /**
+     * 合同段添加表单
+     * 合同段添加表单
+     */
+    public void insertContractForm(ContractInfo contractInfo, List<WbsTreePrivate> wbsTreePrivates, List<WbsTreeContract> addContractNodes) {
+        List<WbsTreeContract> addData = new ArrayList<>();
+
+
+        if (CollectionUtil.isNotEmpty(addContractNodes)) {
+            //源节点数据
+            List<WbsTreeContract> wbsTreeContracts = BeanUtil.copyProperties(wbsTreePrivates, WbsTreeContract.class);
+
+
+            //选给新增的节点赋值
+            addContractNodes.forEach(f -> {
+                f.setPKeyId(SnowFlakeUtil.getId());
+            });
+
+            for (WbsTreeContract addContractNode : addContractNodes) {
+                if (addContractNode.getParentId() == 0) {
+                    continue;
+                }
+
+                //查询出当前模板节点的父节点 去获取对应项目节点  如果父节点为0就跳过
+                List<WbsTreeContract> addContractParentNodes = wbsTreeContracts.stream().filter(f -> f.getTreePId().equals(addContractNode.getParentId())).collect(Collectors.toList());
+
+                //如果没有数据  就表示这条数据的父节点也时新增节点 就需要从新增节点集合中找父级节点
+                if (addContractParentNodes.isEmpty()) {
+                    addContractParentNodes = addContractNodes.stream().filter(f -> f.getId().equals(addContractNode.getParentId())).collect(Collectors.toList());
+                }
+                //如果现在还找不到当前节点的父节点就表示数据有问题
+                if (addContractParentNodes.isEmpty()) {
+                    //TODO
+                    continue;
+                }
+                //当前新增节点的父节点
+                WbsTreeContract parent = addContractParentNodes.get(0);
+
+                addContractNode.setIsBussShow(1);
+                addContractNode.setIsDeleted(0);
+                addContractNode.setStatus(1);
+                //记录原始表的pKeyId
+                addContractNode.setIsTypePrivatePid(parent.getPKeyId());
+                addContractNode.setParentId(parent.getId());
+                addContractNode.setAncestors(parent.getAncestors() + "," + parent.getId());
+                //TODO
+                addContractNode.setTreePId(addContractNode.getId());
+                //TODO
+                addContractNode.setPId(parent.getPKeyId());
+                //TODO
+                addContractNode.setContractId(parent.getContractId());
+                addContractNode.setContractId(String.valueOf(contractInfo.getId()));
+                //更新创建时间
+                addContractNode.setCreateTime(DateTime.now());
+            }
+            addData.addAll(addContractNodes);
+        }
+
+
+        //查询出当前项目所有节点---------------------------------------------------------------------------------------------------
+        List<WbsTreeContract> addList = wbsTreeContractMapper.selectList(Wrappers.<WbsTreeContract>lambdaQuery()
+                .select(WbsTreeContract::getPKeyId, WbsTreeContract::getPId)
+                .eq(WbsTreeContract::getIsDeleted, 0)
+                .eq(WbsTreeContract::getContractId, contractInfo.getId()));
+
+        addList.addAll(addData);
+
+
+        //组合祖级路径 根据当前选中节点为开始
+        Map<Long, WbsTreeContract> collect = addList.stream().collect(Collectors.toMap(WbsTreeContract::getPKeyId, Function.identity()));
+
+        addData.forEach(node -> {
+            //通过转换为WbsTreePrivate的方式 去获取祖级节点
+            String correctAncestors = createAncestorsPId(node, collect);
+            node.setAncestorsPId(correctAncestors);
+        });
+
+        //新增-----------------------------------------------------------------------------------------------------------------
+        List<List<WbsTreeContract>> partition = Lists.partition(addData, 1000);
+        for (List<WbsTreeContract> data : partition) {
+            //单个批次一个事务,只会回滚当前批次数据
+            Integer i = wbsTreeContractMapper.insertBatchSomeColumn(data);
+            //如果失败  -- - - - - 继续执行   或者把当前节点的p_key_id 记录到某个地方 方便后续处理
+            if (i == 0) {
+                List<Long> collect1 = addData.stream().map(WbsTreeContract::getPKeyId).collect(Collectors.toList());
+                //这里可以保存到数据库指定错误日志表
+            }
+        }
+    }
+
+
+    /**
+     * 项目同步电签
+     *
+     * @param collect          需要修改的节点
+     * @param templateId       同步源项目ID
+     * @param projectId        当前项目ID
+     * @param editPrivateNodes 模板对应的节点
+     * @param editPrivateNodes 需要修改的节点
+     */
+    private void updateEViSa(List<Integer> collect, Long templateId, Long projectId, List<WbsTreePrivate> treePrivates,List<WbsTreePrivate> editPrivateNodes) {
+
+
+
+        //封装map
+
+        List<Long> privateIds = editPrivateNodes.stream().map(WbsTreePrivate::getPKeyId).collect(Collectors.toList());
+
+        //源项目电签和默认值信息
+        List<TextdictInfo> tempTextDictInfo = textdictInfoMapper.selectList(Wrappers.<TextdictInfo>lambdaQuery()
+                .eq(TextdictInfo::getProjectId, templateId)
+                .eq(TextdictInfo::getIsDeleted, 0)
+                .in(TextdictInfo::getTabId, privateIds)
+                .in(TextdictInfo::getType, 2, 4, 6));
+
+        Map<Integer, List<TextdictInfo>> tempTextDictInfoMap = tempTextDictInfo.stream().collect(Collectors.groupingBy(TextdictInfo::getType));
+
+
+
+
+
+        //电签
+        if (collect.contains(3)) {
+            List<TextdictInfo> tempViSa = new ArrayList<>();
+            //个人电签
+            List<TextdictInfo> tempMyViSa = tempTextDictInfoMap.get(2);
+            if (CollectionUtil.isNotEmpty(tempMyViSa)) {
+                tempViSa.addAll(tempMyViSa);
+
+            }
+            //企业电签
+            List<TextdictInfo> tempEnterViSa = tempTextDictInfoMap.get(6);
+            if (CollectionUtil.isNotEmpty(tempEnterViSa)) {
+                tempViSa.addAll(tempEnterViSa);
+            }
+
+            if (CollectionUtil.isNotEmpty(tempViSa)) {
+                for (TextdictInfo textdictInfo : tempViSa) {
+                    textdictInfo.setId(SnowFlakeUtil.getId());
+                    textdictInfo.setProjectId(String.valueOf(projectId));
+                }
+            }
+
+        }
+        //默认值
+        if (collect.contains(5)) {
+            List<TextdictInfo> tempDefault = tempTextDictInfoMap.get(4);
+            if(CollectionUtil.isNotEmpty(tempDefault)){
+
+                for (TextdictInfo textdictInfo : tempDefault) {
+                    textdictInfo.setId(SnowFlakeUtil.getId());
+                    textdictInfo.setProjectId(String.valueOf(projectId));
+                }
+
+            }
+        }
+
+
+    }
+
+
+    /**
+     * 20250414-lhb-新增
+     * 创建祖级路径
+     * <p>
+     * 该方法用于构建给定节点的祖先路径标识符(PId)字符串
+     * 它通过追溯节点的父节点,直到达到根节点或满足特定条件为止
+     *
+     * @param node    WbsTreeContract类型的节点,表示需要构建路径的起始节点
+     * @param nodeMap 一个映射,其键为节点ID,值为WbsTreeContract类型的节点对象,用于快速查找节点
+     * @return 返回一个字符串,表示构建的祖先路径标识符序列,以逗号分隔
+     */
+    private String createAncestorsPId(WbsTreePrivate node, Map<Long, WbsTreePrivate> nodeMap) {
+        // 初始化路径列表,用于存储祖先节点的ID
+        List<Long> path = new ArrayList<>();
+        // 从给定的节点开始
+        WbsTreePrivate 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())) {
+                break;
+            }
+            // 将当前节点的ID添加到已访问集合中
+            visited.add(current.getPKeyId());
+
+            // 从映射中获取当前节点的父节点
+            current = nodeMap.get(current.getPId());
+            // 如果父节点存在,则将其ID添加到路径列表的开头
+            if (current != null) {
+                path.add(0, current.getPKeyId());
+            }
+
+            // 安全限制,防止路径过长导致性能问题
+            if (path.size() > 50) {
+                break;
+            }
+        }
+        // 将根节点ID(0)添加到路径的最前面,表示路径的起点
+        path.add(0, 0L);
+        // 将路径列表转换为字符串并返回
+        return String.join(",", path.stream().map(String::valueOf).toArray(String[]::new));
+    }
+
+    /**
+     * 20250414-lhb-新增
+     * 创建祖级路径
+     * <p>
+     * 该方法用于构建给定节点的祖先路径标识符(PId)字符串
+     * 它通过追溯节点的父节点,直到达到根节点或满足特定条件为止
+     *
+     * @param node    WbsTreeContract类型的节点,表示需要构建路径的起始节点
+     * @param nodeMap 一个映射,其键为节点ID,值为WbsTreeContract类型的节点对象,用于快速查找节点
+     * @return 返回一个字符串,表示构建的祖先路径标识符序列,以逗号分隔
+     */
+    private String createAncestorsPId(WbsTreeContract node, Map<Long, WbsTreeContract> nodeMap) {
+        // 初始化路径列表,用于存储祖先节点的ID
+        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())) {
+                break;
+            }
+            // 将当前节点的ID添加到已访问集合中
+            visited.add(current.getPKeyId());
+
+            // 从映射中获取当前节点的父节点
+            current = nodeMap.get(current.getPId());
+            // 如果父节点存在,则将其ID添加到路径列表的开头
+            if (current != null) {
+                path.add(0, current.getPKeyId());
+            }
+
+            // 安全限制,防止路径过长导致性能问题
+            if (path.size() > 50) {
+                break;
+            }
+        }
+        // 将根节点ID(0)添加到路径的最前面,表示路径的起点
+        path.add(0, 0L);
+        // 将路径列表转换为字符串并返回
+        return String.join(",", path.stream().map(String::valueOf).toArray(String[]::new));
+    }
+
+
+    /**
+     * 根据选中节点id 同步新增节点和新增表单
+     *
+     * @param primaryKeyId
+     * @return
+     */
+
+    public void test(String primaryKeyId) {
+
+        if (StringUtils.isNotEmpty(primaryKeyId)) {
+            //获取当前节点对应节点信息
+            WbsTreePrivate wbsTreePrivate = wbsTreePrivateMapper.selectOne(Wrappers.<WbsTreePrivate>lambdaQuery().eq(WbsTreePrivate::getPKeyId, primaryKeyId));
+
+            //获取项目信息
+            ProjectInfo projectInfo = projectInfoMapper.selectOne(Wrappers.<ProjectInfo>lambdaQuery().eq(ProjectInfo::getId, wbsTreePrivate.getProjectId()));
+
+            //当前项目模板对应的节点信息
+
+            List<WbsTreePrivate> templateNodes = new ArrayList<>();
+
+            //结果集
+            List<WbsTreePrivate> addData = new ArrayList<>();
+            //质检
+            //当前节点引用的模板为公有模板
+
+            if (("public").equals(projectInfo.getReferenceWbsTemplateType())) {
+                //当前节点对应的模板节点信息
+                WbsTree wbsTreeTemplate = wbsTreeMapper.selectOne(Wrappers.<WbsTree>lambdaQuery()
+                        .eq(WbsTree::getId, wbsTreePrivate.getTreePId()));
+                //获取模板中当前节点的子节点的数据------------------------------------------------------------------------------------------------------------------------
+                LambdaQueryWrapper<WbsTree> wrapperTree = Wrappers.lambdaQuery();
+                wrapperTree.eq(WbsTree::getWbsId, wbsTreePrivate.getWbsId())
+                        .eq(WbsTree::getIsDeleted, 0);
+                if (wbsTreeTemplate.getParentId() != 0) {
+                    wrapperTree.apply("FIND_IN_SET({0},ancestors)", wbsTreeTemplate.getId());
+                }
+                //模板节点的所有子节点信息
+                List<WbsTree> wbsTrees = wbsTreeMapper.selectList(wrapperTree);
+                //3、获取需要新增的节点或者表单节点信息 以及他们对应的父级节点信息--------------------------------------------------------------------------------------------
+                if (CollectionUtil.isEmpty(wbsTrees)) {
+                    throw new ServiceException("模板节点未找到");
+                }
+                templateNodes = BeanUtil.copyProperties(wbsTrees, WbsTreePrivate.class);
+
+            } else if (("private").equals(projectInfo.getReferenceWbsTemplateType())) {
+                //根据wbsTreePrivate的wbsId=私有引用的pKeyId来获取引用树根节点
+                WbsTreePrivate wbsTreePrivateRoot = wbsTreePrivateMapper.selectOne(Wrappers.<WbsTreePrivate>lambdaQuery()
+                        .eq(WbsTreePrivate::getPKeyId, wbsTreePrivate.getWbsId()));
+
+                //获取当前选中节点与私有模板对应的节点信息 父级模板项目的当前选中节点
+                wbsTreePrivateRoot = wbsTreePrivateMapper.selectOne(Wrappers.<WbsTreePrivate>lambdaQuery()
+                        .eq(WbsTreePrivate::getProjectId, wbsTreePrivateRoot.getProjectId())
+                        .eq(WbsTreePrivate::getTreePId, wbsTreePrivate.getTreePId()));
+
+                //获取模板中当前节点的子节点的数据------------------------------------------------------------------------------------------------------------------------
+                LambdaQueryWrapper<WbsTreePrivate> wrapperPrivate = Wrappers.lambdaQuery();
+                wrapperPrivate.eq(WbsTreePrivate::getProjectId, wbsTreePrivateRoot.getProjectId())
+                        .eq(WbsTreePrivate::getIsDeleted, 0);
+                //判断如果为顶级顶级节点
+                if (wbsTreePrivate.getTreePId() != 0) {
+                    wrapperPrivate.apply("FIND_IN_SET({0},ancestors_p_id)", wbsTreePrivateRoot.getPId());
+                }
+                templateNodes = wbsTreePrivateMapper.selectList(wrapperPrivate);
+            }
+
+            if (CollectionUtil.isEmpty(templateNodes)) {
+                throw new ServiceException("未找到模板对应节点");
+            }
+
+
+            //2、获取当前节点的子节点的数据------------------------------------------------------------------------------------------------------------------------
+            LambdaQueryWrapper<WbsTreePrivate> wrapperPrivate = Wrappers.lambdaQuery();
+            wrapperPrivate.eq(WbsTreePrivate::getWbsId, wbsTreePrivate.getWbsId()).eq(WbsTreePrivate::getIsDeleted, 0);
+            //判断如果为顶级顶级节点
+            if (wbsTreePrivate.getTreePId() != 0) {
+                wrapperPrivate.apply("FIND_IN_SET({0},ancestors_p_id)", wbsTreePrivate.getPId());
+            }
+            List<WbsTreePrivate> wbsTreePrivates = wbsTreePrivateMapper.selectList(wrapperPrivate);
+
+            if (wbsTreePrivates.isEmpty()) {
+                throw new ServiceException("无法找到模板对应节点,请检查模板节点");
+            }
+            //获取id 和 tree_p_id 组成的集合
+            Set<Long> ids = wbsTreePrivates.stream().map(WbsTreePrivate::getTreePId).collect(Collectors.toSet());
+            //3.1筛选出需要新增的节点
+            List<WbsTreePrivate> addPrivateNodes = templateNodes.stream()
+                    .filter(f -> !ids.contains(f.getId()))
+                    .collect(Collectors.toList());
+            //------------------------------------------------新增-------------------------------------------------------------------------
+            if (CollectionUtil.isNotEmpty(addPrivateNodes)) {
+                //先给每个新增节点赋唯一id,项目id,父级id
+                addPrivateNodes.forEach(f -> f.setPKeyId(SnowFlakeUtil.getId()));
+
+                //给每一个节点的父级id
+                for (WbsTreePrivate addPrivateNode : addPrivateNodes) {
+                    if (addPrivateNode.getParentId() == 0) {
+                        continue;
+                    }
+
+                    //查询出当前模板节点的父节点 去获取对应项目节点  如果父节点为0就跳过
+                    List<WbsTreePrivate> addPrivateParentNodes = wbsTreePrivates.stream().filter(f -> f.getTreePId().equals(addPrivateNode.getParentId())).collect(Collectors.toList());
+                    //如果没有数据  就表示这条数据的父节点也时新增节点 就需要从新增节点集合中找父级节点
+                    if (addPrivateParentNodes.isEmpty()) {
+                        addPrivateParentNodes = addPrivateNodes.stream().filter(f -> f.getId().equals(addPrivateNode.getParentId())).collect(Collectors.toList());
+                    }
+                    //如果现在还找不到当前节点的父节点就表示数据有问题
+                    if (addPrivateParentNodes.isEmpty()) {
+                        //TODO
+                        continue;
+                    }
+                    //当前新增节点的父节点
+                    WbsTreePrivate parent = addPrivateParentNodes.get(0);
+
+                    addPrivateNode.setPId(parent.getPKeyId());
+                    addPrivateNode.setWbsId(parent.getWbsId());
+                    addPrivateNode.setWbsType(wbsTreePrivate.getWbsType());
+                    addPrivateNode.setIsAddConceal(0);
+                    addPrivateNode.setProjectId(wbsTreePrivate.getProjectId());
+                    //TODO 后续如果把p_key_id改成了id做 唯一id
+                    addPrivateNode.setTreePId(addPrivateNode.getId());
+                    //后续如果使用id做当前表主键 则先在赋值treePid之后再去赋值id    addPrivateNode.setId(SnowFlakeUtil.getId());
+                }
+                addData.addAll(addPrivateNodes);
+            }
+
+
+            //设置html_url----------------------------------------------------------------------------------------------------------
+            //如果有新增的数据 旧设置html_url
+            if (CollectionUtil.isNotEmpty(addData)) {
+                try {
+                    String file_path = ParamCache.getValue(CommonConstant.SYS_LOCAL_URL);
+                    Set<String> urls = new HashSet<>();
+                    for (WbsTreePrivate tree : addData) {
+                        if (org.apache.commons.lang3.StringUtils.isNotBlank(tree.getHtmlUrl())) {
+                            String[] split = tree.getHtmlUrl().split("/");
+                            String htmlUrl = file_path + "privateUrlCopy/" + tree.getPdfUrl() + "/" + split[split.length - 1];
+                            if (!urls.contains(tree.getHtmlUrl())) {
+                                urls.add(tree.getHtmlUrl());
+                                File file_in = ResourceUtil.getFile(tree.getHtmlUrl());
+                                if (!file_in.exists() || file_in.length() == 0) {
+                                    continue;
+                                }
+                                File file_out = ResourceUtil.getFile(htmlUrl);
+                                FileUtil.copy(file_in, file_out);
+                            }
+                            tree.setHtmlUrl(htmlUrl);
+                        }
+                    }
+                } catch (Exception e) {
+                    throw new ServiceException("重置表单路径错误");
+                }
+                //查询出当前项目所有节点---------------------------------------------------------------------------------------------------
+                List<WbsTreePrivate> addList = wbsTreePrivateMapper.selectList(Wrappers.<WbsTreePrivate>lambdaQuery()
+                        .select(WbsTreePrivate::getPKeyId, WbsTreePrivate::getPId)
+                        .eq(WbsTreePrivate::getIsDeleted, 0)
+                        .eq(WbsTreePrivate::getProjectId, wbsTreePrivate.getProjectId()));
+
+                addList.addAll(addData);
+                //组合祖级路径 根据当前选中节点为开始
+                Map<Long, WbsTreePrivate> collect = addList.stream().collect(Collectors.toMap(WbsTreePrivate::getPKeyId, Function.identity()));
+
+                addData.forEach(node -> {
+                    String correctAncestors = createAncestorsPId(node, collect);
+                    node.setAncestorsPId(correctAncestors);
+                });
+
+                //新增-----------------------------------------------------------------------------------------------------------------
+                List<List<WbsTreePrivate>> partition = Lists.partition(addData, 1000);
+                for (List<WbsTreePrivate> data : partition) {
+                    //单个批次一个事务,只会回滚当前批次数据
+                    Integer i = wbsTreePrivateMapper.insertBatchSomeColumn(data);
+                    //如果失败  -- - - - - 继续执行   或者把当前节点的p_key_id 记录到某个地方 方便后续处理
+                    if (i == 0) {
+                        List<Long> collect1 = addData.stream().map(WbsTreePrivate::getPKeyId).collect(Collectors.toList());
+                        //这里可以保存到数据库指定错误日志表
+                        throw new ServiceException("新增节点失败,请联系后台人员");
+                    }
+                }
+                //记录同步信息
+            }
+        } else {
+            throw new ServiceException("未传指定参数,请联系后台人员");
+        }
+    }
+}

+ 129 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsTreeSynchronousRecordServiceImpl.java

@@ -0,0 +1,129 @@
+package org.springblade.manager.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springblade.common.utils.SnowFlakeUtil;
+import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.secure.BladeUser;
+import org.springblade.core.secure.utils.SecureUtil;
+import org.springblade.manager.entity.WbsTreePrivate;
+import org.springblade.manager.entity.WbsTreeSynchronousRecord;
+import org.springblade.manager.mapper.WbsTreePrivateMapper;
+import org.springblade.manager.mapper.WbsTreeSynchronousRecordMapper;
+import org.springblade.manager.service.WbsTreeSynchronousRecordService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author LHB
+ * @description 针对表【m_wbs_tree_synchronous_record(WBS同步记录表)】的数据库操作Service实现
+ * @createDate 2025-05-15 13:52:09
+ */
+@Service
+public class WbsTreeSynchronousRecordServiceImpl extends ServiceImpl<WbsTreeSynchronousRecordMapper, WbsTreeSynchronousRecord>
+        implements WbsTreeSynchronousRecordService {
+
+    @Autowired
+    private WbsTreePrivateMapper wbsTreePrivateMapper;
+
+    @Autowired
+    private WbsSynchronousServiceImpl wbsSynchronousService;
+
+    @Override
+    public Integer insert(WbsTreeSynchronousRecord mWbsTreeSynchronousRecord) {
+        //新增之前  判断当前选中节点是否在同步列表中,因为是多节点 所有要查询所有同项目下的同步节点再获取所有子节点 判断当前选中节点是否在这一批次当中
+        List<WbsTreeSynchronousRecord> wbsTreeSynchronousRecords = baseMapper.selectList(new QueryWrapper<WbsTreeSynchronousRecord>().lambda()
+                .select(WbsTreeSynchronousRecord::getNodeId)
+                .eq(WbsTreeSynchronousRecord::getProjectId, mWbsTreeSynchronousRecord.getProjectId())
+                .eq(WbsTreeSynchronousRecord::getIsDeleted, 0)
+                .in(WbsTreeSynchronousRecord::getStatus, 1, 2));
+        List<String> nodeIds = wbsTreeSynchronousRecords.stream().map(WbsTreeSynchronousRecord::getNodeId).collect(Collectors.toList());
+
+        //所有子节点集合
+        List<Long> privateIds = new ArrayList<>();
+        //通过 ancestors_p_id 查询所有 非表单子节点
+        for (String nodeId : nodeIds) {
+            String[] split = nodeId.split(",");
+            for (String s : split) {
+                List<WbsTreePrivate> wbsTreePrivates = wbsTreePrivateMapper.selectList(new QueryWrapper<WbsTreePrivate>().lambda()
+                        .select(WbsTreePrivate::getPKeyId)
+                        .eq(WbsTreePrivate::getIsDeleted, 0)
+                        .eq(WbsTreePrivate::getType, 1)
+                        .apply("find_in_set({0},ancestors_p_id)", s)
+                );
+                privateIds.add(Long.valueOf(s));
+                privateIds.addAll(wbsTreePrivates.stream().map(WbsTreePrivate::getPKeyId).collect(Collectors.toList()));
+            }
+        }
+
+        //记录选中节点下的表单数
+        int count = 0;
+        String[] newNodeIds = mWbsTreeSynchronousRecord.getNodeId().split(",");
+        for (String newNodeId : newNodeIds) {
+            Long nodeIdLong = Long.valueOf(newNodeId);
+            if (privateIds.contains(nodeIdLong)) {
+                throw new ServiceException("当前选中节点正在同步,请勿重复同步");
+            }
+            //计算当前选中节点的所有子节点表单数量
+            List<WbsTreePrivate> wbsTreePrivates = wbsTreePrivateMapper.selectList(new QueryWrapper<WbsTreePrivate>().lambda()
+                    .select(WbsTreePrivate::getPKeyId)
+                    .eq(WbsTreePrivate::getIsDeleted, 0)
+                    .eq(WbsTreePrivate::getType, 2)
+                    .apply("find_in_set({0},ancestors_p_id)", newNodeId)
+            );
+            count += wbsTreePrivates.size();
+        }
+
+        mWbsTreeSynchronousRecord.setId(SnowFlakeUtil.getId());
+        //获取当前用户
+        BladeUser user = SecureUtil.getUser();
+        mWbsTreeSynchronousRecord.setCreateUserId(user.getUserId());
+        mWbsTreeSynchronousRecord.setCreateUser(user.getNickName());
+        //新增
+        baseMapper.insert(mWbsTreeSynchronousRecord);
+
+        return count;
+    }
+
+
+    /**
+     * 同步节点表单
+     *  定时检查同步任务,状态为1的数据如果最后更新时间与当前时间超过10分钟,则修改状态为1
+     */
+    @Scheduled(fixedDelay = 10000)
+    public void syncInit() {
+        List<WbsTreeSynchronousRecord> wbsTreeSynchronousRecords = baseMapper.selectList(new QueryWrapper<WbsTreeSynchronousRecord>().lambda()
+                .in(WbsTreeSynchronousRecord::getStatus, 0,1)
+                .eq(WbsTreeSynchronousRecord::getIsDeleted, 0)
+                .orderByAsc(WbsTreeSynchronousRecord::getCreateTime)
+                .last("limit 10")
+        );
+
+        for (WbsTreeSynchronousRecord wbsTreeSynchronousRecord : wbsTreeSynchronousRecords) {
+            if(wbsTreeSynchronousRecord.getStatus() == 1){
+                if (wbsTreeSynchronousRecord.getUpdateTime().getTime() + 600000 < System.currentTimeMillis()) {
+                    wbsTreeSynchronousRecord.setStatus(0);
+                    baseMapper.updateById(wbsTreeSynchronousRecord);
+                }
+            }else{
+                //通过线程池执行同步任务
+                wbsSynchronousService.syncExecute(wbsTreeSynchronousRecord);
+                //修改数据状态为正在同步
+                wbsTreeSynchronousRecord.setStatus(1);
+                baseMapper.updateById(wbsTreeSynchronousRecord);
+            }
+
+
+        }
+    }
+}
+
+
+
+