瀏覽代碼

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

LHB 3 周之前
父節點
當前提交
9db447109a
共有 18 個文件被更改,包括 527 次插入71 次删除
  1. 61 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/entity/WbsTreeContractOldHtml.java
  2. 3 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/feign/WbsTreeContractClient.java
  3. 25 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/feign/WbsTreeContractOldHtmlClient.java
  4. 45 0
      blade-service/blade-business/src/main/java/org/springblade/business/controller/InformationWriteQueryController.java
  5. 45 0
      blade-service/blade-business/src/main/java/org/springblade/business/utils/FileUtils.java
  6. 10 1
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/ExcelTabController.java
  7. 4 1
      blade-service/blade-manager/src/main/java/org/springblade/manager/feign/WbsTreeContractClientImpl.java
  8. 33 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/feign/WbsTreeContractOldHtmlClientImpl.java
  9. 18 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/WbsTreeContractOldHtmlMapper.java
  10. 22 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/WbsTreeContractOldHtmlMapper.xml
  11. 2 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/IWbsTreeContractService.java
  12. 13 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/WbsTreeContractOldHtmlService.java
  13. 32 6
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ExcelTabServiceImpl.java
  14. 61 6
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsSynchronousEViSaServiceImpl.java
  15. 84 55
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsSynchronousServiceImpl.java
  16. 22 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsTreeContractOldHtmlServiceImpl.java
  17. 6 2
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsTreeContractServiceImpl.java
  18. 41 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/utils/FileUtils.java

+ 61 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/entity/WbsTreeContractOldHtml.java

@@ -0,0 +1,61 @@
+package org.springblade.manager.entity;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.util.Date;
+import lombok.Data;
+
+/**
+ * 合同段表单上报之前的html记录
+ * @author LHB
+ * @TableName m_wbs_tree_contract_old_html
+ */
+@TableName(value ="m_wbs_tree_contract_old_html")
+@Data
+public class WbsTreeContractOldHtml {
+    /**
+     * 
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 合同WBS的p_key_id
+     */
+    private Long contractFormId;
+
+    /**
+     * 上报之前的html表单
+     */
+    private String oldHtmlUrl;
+
+    /**
+     * 上报之前的html表单
+     */
+    private String oldExcelUrl;
+
+    /**
+     *  是否删除(0-正常,1-已删除)
+     */
+    private Integer isDeleted;
+
+    /**
+     *  创建时间
+     */
+    private Date createTime;
+
+    /**
+     *  创建人
+     */
+    private Long createUser;
+
+    /**
+     *  修改时间
+     */
+    private Date updateTime;
+
+    /**
+     *  修改人
+     */
+    private Long updateUser;
+}

+ 3 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/feign/WbsTreeContractClient.java

@@ -202,4 +202,7 @@ public interface WbsTreeContractClient {
 
     @GetMapping(API_PREFIX + "/get-ekey")
     EKeyDto getEKey(@RequestParam String contractId, @RequestParam Long pKeyId, @RequestParam String wbsId);
+
+    @PostMapping(API_PREFIX + "/queryListByPIds")
+    List<WbsTreeContract> queryListByPIds(@RequestBody List<Long> pIds);
 }

+ 25 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/feign/WbsTreeContractOldHtmlClient.java

@@ -0,0 +1,25 @@
+package org.springblade.manager.feign;
+
+import org.springblade.manager.entity.WbsTreeContract;
+import org.springblade.manager.entity.WbsTreeContractOldHtml;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+
+import java.util.List;
+
+import static org.springblade.core.launch.constant.AppConstant.APPLICATION_NAME_PREFIX;
+
+@FeignClient(value = APPLICATION_NAME_PREFIX + "manager")
+public interface WbsTreeContractOldHtmlClient {
+    /**
+     * 接口前缀
+     */
+    String API_PREFIX = "/api/manager/WbsTreeContractOldHtml";
+
+    @PostMapping(API_PREFIX + "/save")
+    Boolean save(@RequestBody List<WbsTreeContractOldHtml> data);
+
+    @PostMapping(API_PREFIX + "/deleteByContractFormIds")
+    void deleteByContractFormIds(@RequestBody List<Long> collect);
+}

+ 45 - 0
blade-service/blade-business/src/main/java/org/springblade/business/controller/InformationWriteQueryController.java

@@ -7,6 +7,7 @@ import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
@@ -79,6 +80,9 @@ import javax.servlet.http.HttpServletResponse;
 import javax.validation.Valid;
 import java.io.*;
 import java.net.URLEncoder;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
@@ -105,6 +109,7 @@ public class InformationWriteQueryController extends BladeController {
     private final ContractClient contractClient;
 
     private final WbsTreeContractClient wbsTreeContractClient;
+    private final WbsTreeContractOldHtmlClient wbsTreeContractOldHtmlClient;
 
     private final WbsTreePrivateClient wbsTreePrivateClient;
 
@@ -1034,8 +1039,48 @@ public class InformationWriteQueryController extends BladeController {
                 return R.data(300, false, "未查询到填报信息,上报失败");
             }
         } else { //质检
+            //记录选中节点的所有表单旧html
+            List<Long> list = Arrays.stream(startTaskVO.getIds().split(",")).map(Long::parseLong).collect(Collectors.toList());
+            List<WbsTreeContract> wbsTreeContracts =  wbsTreeContractClient.queryListByPIds(list);
+
+
             businessData = this.informationQueryService.getOne(Wrappers.<InformationQuery>lambdaQuery().eq(InformationQuery::getWbsId, startTaskVO.getIds().replaceAll(",", "")).eq(InformationQuery::getClassify, startTaskVO.getClassify().toString()).eq(InformationQuery::getType, 1).last("order by id desc limit 1"));
             if (businessData != null) {
+
+                //处理html 复制之后记录在新表中w
+                if (CollectionUtils.isNotEmpty(wbsTreeContracts)) {
+                    List<WbsTreeContractOldHtml> data = new ArrayList<>();
+                    try {
+                        for (WbsTreeContract wbsTreeContract : wbsTreeContracts) {
+                            ExcelTab byId = excelTabClient.getById(String.valueOf(wbsTreeContract.getExcelId()));
+                            WbsTreeContractOldHtml oldHtml = new WbsTreeContractOldHtml();
+                            oldHtml.setId(SnowFlakeUtil.getId());
+                            oldHtml.setCreateUser(getUser().getUserId());
+                            String htmlUrl = wbsTreeContract.getHtmlUrl();
+                            // 获取或下载文件
+                            Path sourcePath = FileUtils.getOrDownloadFile(htmlUrl);
+                            // 生成副本路径
+                            Path copyPath = FileUtils.generateCopyPath(sourcePath);
+                            // 执行复制操作(覆盖已存在的文件)
+                            Files.copy(sourcePath, copyPath, StandardCopyOption.REPLACE_EXISTING);
+
+                            oldHtml.setContractFormId(wbsTreeContract.getPKeyId());
+                            oldHtml.setOldHtmlUrl(copyPath.toFile().getAbsolutePath());
+                            //记录历史excel路径
+                            oldHtml.setOldExcelUrl(byId.getFileUrl());
+                            data.add(oldHtml);
+                        }
+                        List<Long> collect = data.stream().map(WbsTreeContractOldHtml::getContractFormId).collect(Collectors.toList());
+                        //删除旧记录
+                        wbsTreeContractOldHtmlClient.deleteByContractFormIds(collect);
+
+                        wbsTreeContractOldHtmlClient.save(data);
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                        throw new ServiceException(e.getMessage());
+                    }
+                }
+
                 //设置业务数据ID
                 startTaskVO.setIds(businessData.getId().toString());
                 return this.batchTask(startTaskVO);

+ 45 - 0
blade-service/blade-business/src/main/java/org/springblade/business/utils/FileUtils.java

@@ -17,6 +17,7 @@ import org.springblade.common.constant.CommonConstant;
 import org.springblade.common.utils.CommonUtil;
 import org.springblade.common.utils.SystemUtils;
 import org.springblade.common.vo.DataVO;
+import org.springblade.core.log.exception.ServiceException;
 import org.springblade.core.tool.utils.IoUtil;
 import org.springblade.system.cache.ParamCache;
 
@@ -30,7 +31,9 @@ import java.awt.geom.AffineTransform;
 import java.awt.image.AffineTransformOp;
 import java.awt.image.BufferedImage;
 import java.io.*;
+import java.net.URL;
 import java.net.URLEncoder;
+import java.nio.file.*;
 import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
@@ -366,4 +369,46 @@ public class FileUtils {
         String path = sys_file_net_url + fileUrl.replaceAll("//", "/").replaceAll(file_path2, "");
         return path;
     }
+
+    /**
+     * 获取本地文件,若不存在则从URL下载
+     */
+    public static Path getOrDownloadFile(String localPath) throws IOException {
+        Path path = Paths.get(localPath);
+
+        // 如果本地文件不存在,则从URL下载
+        if (!Files.exists(path)) {
+            System.out.println("本地文件不存在,开始下载...");
+            // 确保父目录存在
+            Path parentDir = path.getParent();
+            if (parentDir != null && !Files.exists(parentDir)) {
+                Files.createDirectories(parentDir);
+            }
+
+            // 从URL下载文件
+            try (InputStream in = CommonUtil.getOSSInputStream(getNetUrl(localPath))) {
+                Files.copy(in, path);
+                System.out.println("文件下载成功: " + path);
+            } catch (IOException e) {
+                throw new IOException("下载文件失败: " + e.getMessage(), e);
+            }
+        }
+        return path;
+    }
+
+    /**
+     * 生成带后缀的副本路径
+     */
+    public static Path generateCopyPath(Path originalPath) {
+        String suffix = "_copy";
+        String fileName = originalPath.getFileName().toString();
+        int dotIndex = fileName.lastIndexOf('.');
+
+        // 处理带扩展名和不带扩展名的文件
+        String newName = (dotIndex > 0)
+                ? fileName.substring(0, dotIndex) + suffix + fileName.substring(dotIndex)
+                : fileName + suffix;
+
+        return originalPath.resolveSibling(newName);
+    }
 }

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

@@ -164,6 +164,7 @@ public class ExcelTabController extends BladeController {
 
     private final WbsTreeContractMapper wbsTreeContractMapper;
 
+    private final WbsTreeContractOldHtmlService wbsTreeContractOldHtmlService;
 
     @Autowired
     StringRedisTemplate RedisTemplate;
@@ -2035,7 +2036,13 @@ public class ExcelTabController extends BladeController {
         }
         executionTime.info("----公式填充执行完毕----");
 
-        //把CL08 和CL10 的监理表添加进去
+        //删除旧html数据 重刷电签不允许删除旧html
+        if(dataInfo.get("isNotDelOldHtml") == null){
+            List<String> collect = tableInfoList.stream().map(TableInfo::getPkeyId).collect(Collectors.toList());
+            boolean update = wbsTreeContractOldHtmlService.update(Wrappers.<WbsTreeContractOldHtml>update().lambda()
+                    .set(WbsTreeContractOldHtml::getIsDeleted, 1)
+                    .in(WbsTreeContractOldHtml::getContractFormId, collect));
+        }
 
         //保存数据到数据库
         R<Object> result = this.excelTabService.saveOrUpdateInfo(tableInfoList,singnType);
@@ -4660,6 +4667,8 @@ public class ExcelTabController extends BladeController {
                 }
                 js2.put("orderList", array);
                 js.put("dataInfo", js2);
+                js.put("signType", "1");
+                js.put("isNotDelOldHtml", 1);
                 /*if(infoB!=null && (infoB.getStatus()==1 || infoB.getStatus()==2)){
                     js.put("signType", "1");
                 }*/

+ 4 - 1
blade-service/blade-manager/src/main/java/org/springblade/manager/feign/WbsTreeContractClientImpl.java

@@ -491,5 +491,8 @@ public class WbsTreeContractClientImpl implements WbsTreeContractClient {
         return wbsTreeContractService.getEKey(contractId,pKeyId,wbsId);
     }
 
-
+    @Override
+    public List<WbsTreeContract> queryListByPIds(List<Long> pIds) {
+        return wbsTreeContractService.queryListByPIds(pIds);
+    }
 }

+ 33 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/feign/WbsTreeContractOldHtmlClientImpl.java

@@ -0,0 +1,33 @@
+package org.springblade.manager.feign;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.AllArgsConstructor;
+import org.springblade.manager.entity.WbsTreeContractOldHtml;
+import org.springblade.manager.service.WbsTreeContractOldHtmlService;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * @author LHB
+ */
+@RestController
+@AllArgsConstructor
+public class WbsTreeContractOldHtmlClientImpl implements WbsTreeContractOldHtmlClient {
+    @Resource
+    private WbsTreeContractOldHtmlService wbsTreeContractOldHtmlService;
+
+    @Override
+    public Boolean save(List<WbsTreeContractOldHtml> data) {
+        return wbsTreeContractOldHtmlService.saveBatch(data);
+    }
+
+    @Override
+    public void deleteByContractFormIds(List<Long> collect) {
+        wbsTreeContractOldHtmlService.update(Wrappers.<WbsTreeContractOldHtml>update().lambda()
+                .set(WbsTreeContractOldHtml::getIsDeleted, 1)
+                .eq(WbsTreeContractOldHtml::getIsDeleted, 0)
+                .in(WbsTreeContractOldHtml::getContractFormId, collect));
+    }
+}

+ 18 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/WbsTreeContractOldHtmlMapper.java

@@ -0,0 +1,18 @@
+package org.springblade.manager.mapper;
+
+import org.springblade.manager.entity.WbsTreeContractOldHtml;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+* @author LHB
+* @description 针对表【m_wbs_tree_contract_old_html(合同段表单上报之前的html记录)】的数据库操作Mapper
+* @createDate 2025-07-01 17:37:36
+* @Entity generator.domain.MWbsTreeContractOldHtml
+*/
+public interface WbsTreeContractOldHtmlMapper extends BaseMapper<WbsTreeContractOldHtml> {
+
+}
+
+
+
+

+ 22 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/WbsTreeContractOldHtmlMapper.xml

@@ -0,0 +1,22 @@
+<?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.WbsTreeContractOldHtmlMapper">
+
+    <resultMap id="BaseResultMap" type="org.springblade.manager.entity.WbsTreeContractOldHtml">
+            <id property="id" column="id" />
+            <result property="contractFormId" column="contract_form_id" />
+            <result property="oldHtmlUrl" column="old_html_url" />
+            <result property="isDeleted" column="is_deleted" />
+            <result property="createTime" column="create_time" />
+            <result property="createUser" column="create_user" />
+            <result property="updateTime" column="update_time" />
+            <result property="updateUser" column="update_user" />
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id,contract_form_id,old_html_url,is_deleted,create_time,create_user,
+        update_time,update_user
+    </sql>
+</mapper>

+ 2 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/IWbsTreeContractService.java

@@ -90,4 +90,6 @@ public interface IWbsTreeContractService extends BaseService<WbsTreeContract> {
     EKeyDto getEKey(String contractId, Long pKeyId, String wbsId);
 
     boolean checkNodeAllDate(WbsTreeContract contract);
+
+    List<WbsTreeContract> queryListByPIds(List<Long> pIds);
 }

+ 13 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/WbsTreeContractOldHtmlService.java

@@ -0,0 +1,13 @@
+package org.springblade.manager.service;
+
+import org.springblade.manager.entity.WbsTreeContractOldHtml;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+* @author LHB
+* @description 针对表【m_wbs_tree_contract_old_html(合同段表单上报之前的html记录)】的数据库操作Service
+* @createDate 2025-07-01 17:37:36
+*/
+public interface WbsTreeContractOldHtmlService extends IService<WbsTreeContractOldHtml> {
+
+}

+ 32 - 6
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ExcelTabServiceImpl.java

@@ -130,6 +130,7 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
     private final TableInfoServiceImpl tableInfoService;
     private final INodeBaseInfoService nodeBaseInfoService;
     private final TrialSelfInspectionRecordClient trialSelfInspectionRecordClient;
+    private final WbsTreeContractOldHtmlService wbsTreeContractOldHtmlService;
 
 
     @Autowired
@@ -1506,6 +1507,15 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
         String keyNameList="";
         // 匹配关联
         try {
+            // 这里先从旧html表中获取  如果有用旧的html m_wbs_tree_contract_old_html
+            WbsTreeContractOldHtml oldHtml = wbsTreeContractOldHtmlService.getOne(Wrappers.<WbsTreeContractOldHtml>lambdaQuery()
+                    .eq(WbsTreeContractOldHtml::getContractFormId, pkeyId)
+                    .eq(WbsTreeContractOldHtml::getIsDeleted, 0)
+                    .last("limit 1"));
+            if(oldHtml != null){
+                wbsTreeContract.setHtmlUrl(oldHtml.getOldHtmlUrl());
+            }
+
             InputStream inputStreamByUrl = FileUtils.getInputStreamByUrl(wbsTreeContract.getHtmlUrl());
             String htmlString = IoUtil.readToString(inputStreamByUrl);
             Document doc = Jsoup.parse(htmlString);
@@ -2115,9 +2125,25 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
         }
 
         //获取清表信息
-        ExcelTab excelTab = this.getById(wbsTreeContract.getExcelId());
-        if (excelTab == null) {
-            return R.fail("未获取到清表信息");
+        String newFileUrl = null;
+        //获取当前表单的历史html
+        // 这里先从旧html表中获取  如果有用旧的html m_wbs_tree_contract_old_html
+        WbsTreeContractOldHtml oldHtml = wbsTreeContractOldHtmlService.getOne(Wrappers.<WbsTreeContractOldHtml>lambdaQuery()
+                .eq(WbsTreeContractOldHtml::getContractFormId, pkeyId)
+                .eq(WbsTreeContractOldHtml::getIsDeleted, 0)
+                .last("limit 1"));
+        if(oldHtml != null){
+            wbsTreeContract.setHtmlUrl(oldHtml.getOldHtmlUrl());
+        }
+        if (oldHtml != null && StringUtils.isNotEmpty(oldHtml.getOldExcelUrl())) {
+            newFileUrl = oldHtml.getOldExcelUrl();
+        } else {
+            //获取清表信息
+            ExcelTab excelTab = this.getById(wbsTreeContract.getExcelId());
+            if (excelTab == null) {
+                return R.fail("未获取到清表信息");
+            }
+            newFileUrl = excelTab.getFileUrl();
         }
 
         Map<String, Object> DataInfo = getBussDataInfo(pkeyId, 0);
@@ -2125,11 +2151,11 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
         Integer realFillRate = 0;
 
         //获取excel流 和 html流
-        InputStream exceInp = CommonUtil.getOSSInputStream(excelTab.getFileUrl());
+        InputStream exceInp = CommonUtil.getOSSInputStream(newFileUrl);
 
         Workbook workbook = null;
-        int index = excelTab.getFileUrl().lastIndexOf(".");
-        String suffix = excelTab.getFileUrl().substring(index);
+        int index = newFileUrl.lastIndexOf(".");
+        String suffix = newFileUrl.substring(index);
 
         if (".xls".equals(suffix)) {
             workbook = new XSSFWorkbook(exceInp);

+ 61 - 6
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsSynchronousEViSaServiceImpl.java

@@ -1,25 +1,33 @@
 package org.springblade.manager.service.impl;
 
 import cn.hutool.core.date.DateTime;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.core.toolkit.StringUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import org.springblade.common.utils.SnowFlakeUtil;
+import org.springblade.core.log.exception.ServiceException;
 import org.springblade.core.tool.utils.CollectionUtil;
 import org.springblade.manager.entity.*;
+import org.springblade.manager.feign.ExcelTabClient;
+import org.springblade.manager.feign.WbsTreeContractOldHtmlClient;
 import org.springblade.manager.mapper.TextdictInfoMapper;
 import org.springblade.manager.mapper.WbsTreeContractMapper;
 import org.springblade.manager.mapper.WbsTreePrivateMapper;
 import org.springblade.manager.mapper.WbsTreeSynchronousRecordMapper;
+import org.springblade.manager.service.WbsTreeContractOldHtmlService;
+import org.springblade.manager.utils.FileUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.*;
 import java.util.stream.Collectors;
 
+import static org.springblade.core.secure.utils.AuthUtil.getUser;
+
 /**
  * @author LHB
  */
@@ -30,6 +38,8 @@ public class WbsSynchronousEViSaServiceImpl {
     private WbsTreePrivateMapper wbsTreePrivateMapper;
     @Autowired
     private WbsTreeContractMapper wbsTreeContractMapper;
+    @Autowired
+    private WbsTreeContractServiceImpl wbsTreeContractService;
     //电签
     @Autowired
     private TextdictInfoMapper textdictInfoMapper;
@@ -40,6 +50,11 @@ public class WbsSynchronousEViSaServiceImpl {
     @Autowired
     private ElementFormulaMappingServiceImpl elementFormulaMappingService;
 
+    @Autowired
+    private WbsTreeContractOldHtmlService wbsTreeContractOldHtmlService;
+    @Autowired
+    private ExcelTabClient excelTabClient;
+
     @Transactional(rollbackFor = Exception.class)
     public void updateTextDictInfo(Long projectId, List<Long> editPrivateIds, List<TextdictInfo> addData) {
         //删除 需要新增的节点的电签和默认值
@@ -154,7 +169,7 @@ public class WbsSynchronousEViSaServiceImpl {
     }
 
     @Transactional(rollbackFor = Exception.class)
-    public void updateContract(String type, Long pId, Long createUserId, List<WbsTreeContract> list) {
+    public void updateContract(String type, Long pId, Long createUserId, List<WbsTreeContract> list, List<WbsTreeContract> wbsTreeContracts) {
         //排序调整
         if (type.contains("7")) {
             list.sort(Comparator.comparingInt(WbsTreeContract::getSort));
@@ -183,8 +198,9 @@ public class WbsSynchronousEViSaServiceImpl {
         for (WbsTreeContract treeContract : list) {
             treeContract.setUpdateTime(DateTime.now());
             treeContract.setUpdateUser(createUserId);
-            wbsTreeContractMapper.updateById(treeContract);
+//            wbsTreeContractMapper.updateById(treeContract);
         }
+        wbsTreeContractService.updateBatchById(list);
 
         //排序调整
         if (type.contains("7")) {
@@ -202,6 +218,45 @@ public class WbsSynchronousEViSaServiceImpl {
                 wbsTreeContractMapper.updateSortBatchByPKeyId(resourceData);
             }
         }
+
+        //处理html 复制之后记录在新表中w
+        if (CollectionUtils.isNotEmpty(wbsTreeContracts)) {
+            List<WbsTreeContractOldHtml> data = new ArrayList<>();
+            try {
+                for (WbsTreeContract wbsTreeContract : wbsTreeContracts) {
+                    ExcelTab byId = excelTabClient.getById(String.valueOf(wbsTreeContract.getExcelId()));
+
+                    //如果有历史记录 并且状态为0 则不记录当前历史记录
+                    long count = wbsTreeContractOldHtmlService.count(Wrappers.<WbsTreeContractOldHtml>lambdaQuery()
+                            .eq(WbsTreeContractOldHtml::getContractFormId, wbsTreeContract.getPKeyId())
+                            .eq(WbsTreeContractOldHtml::getIsDeleted, 0));
+                    if(count > 0){
+                        continue;
+                    }
+
+
+                    WbsTreeContractOldHtml oldHtml = new WbsTreeContractOldHtml();
+                    oldHtml.setId(SnowFlakeUtil.getId());
+                    String htmlUrl = wbsTreeContract.getHtmlUrl();
+                    // 获取或下载文件
+                    Path sourcePath = FileUtils.getOrDownloadFile(htmlUrl);
+                    // 生成副本路径
+                    Path copyPath = FileUtils.generateCopyPath(sourcePath);
+                    // 执行复制操作(覆盖已存在的文件)
+                    Files.copy(sourcePath, copyPath, StandardCopyOption.REPLACE_EXISTING);
+
+                    oldHtml.setContractFormId(wbsTreeContract.getPKeyId());
+                    //记录历史数据 合同段当前 html 当前excel
+                    oldHtml.setOldHtmlUrl(copyPath.toFile().getAbsolutePath());
+                    oldHtml.setOldExcelUrl(byId.getFileUrl());
+                    data.add(oldHtml);
+                }
+                wbsTreeContractOldHtmlService.saveBatch(data);
+            } catch (Exception e) {
+                e.printStackTrace();
+                throw new ServiceException(e.getMessage());
+            }
+        }
     }
 
     @Transactional(rollbackFor = Exception.class)

+ 84 - 55
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsSynchronousServiceImpl.java

@@ -438,7 +438,7 @@ public class WbsSynchronousServiceImpl {
                                 editPrivateNode.setInitTableName(templateNode.getInitTableName());
 
                                 if (StringUtil.isBlank(templateNode.getHtmlUrl())) {
-                                    throw new ServiceException(templateNode.getNodeName() + "HTML文件不存在");
+                                    throw new ServiceException(templateNode.getNodeName()  + "( " + templateNode.getPKeyId() + ")HTML文件不存在");
                                 }
                                 //封装Html路径 根据模板html copy一份到自己项目节点上
                                 String[] split = templateNode.getHtmlUrl().split("/");
@@ -455,7 +455,7 @@ public class WbsSynchronousServiceImpl {
 
                                 if (!file_in.exists() || file_in.length() == 0) {
                                     //如果本地服务器上没有
-                                    throw new ServiceException(templateNode.getNodeName() + "HTML文件不存在");
+                                    throw new ServiceException(templateNode.getNodeName() + "( " + templateNode.getPKeyId() + ")HTML文件不存在");
                                 }
                                 File file_out = ResourceUtil.getFile(htmlUrl);
                                 //查询父级文件夹
@@ -598,6 +598,8 @@ public class WbsSynchronousServiceImpl {
         List<ContractInfo> contractInfos = contractInfoMapper.selectContractIdByProjectId(String.valueOf(wbsTreeSynchronousRecord.getProjectId()));
 
         List<WbsTreeContract> editData = new ArrayList<>();
+        List<WbsTreeContract> oldHtml = new ArrayList<>();
+        StringBuilder errorMsg = new StringBuilder("");
         for (String primaryKeyId : nodeIds) {
 
             //获取当前节点对应节点信息
@@ -616,6 +618,9 @@ public class WbsSynchronousServiceImpl {
                     .eq(WbsTreePrivate::getType, 2)
                     .eq(WbsTreePrivate::getIsDeleted, 0)
                     .apply("FIND_IN_SET({0},ancestors_p_id)", wbsTreePrivate.getPKeyId());
+            if(CollectionUtil.isNotEmpty(formList)){
+                wrapperPrivate.in(WbsTreePrivate::getPKeyId, formList);
+            }
             //当前项目的子节点数据
             List<WbsTreePrivate> wbsTreePrivates = wbsTreePrivateMapper.selectList(wrapperPrivate);
             wbsTreePrivates.add(wbsTreePrivate);
@@ -633,7 +638,8 @@ public class WbsSynchronousServiceImpl {
                 //如果没有查询到,表示该合同下不存在该节点
                 if (CollectionUtil.isEmpty(startContacts)) {
                     //不能抛异常  不然就会中止程序  开发阶段先抛异常,后续统一处理
-                    throw new ServiceException("当前节点不存在");
+//                    throw new ServiceException("当前节点不存在");
+                    continue;
                 }
                 for (WbsTreeContract wbsTreeContract : startContacts) {
                     //获取合同 当前节点的所有子节点数据
@@ -642,73 +648,98 @@ public class WbsSynchronousServiceImpl {
                             .eq(WbsTreeContract::getContractId, contractInfo.getId())
                             .eq(WbsTreeContract::getIsDeleted, 0)
                             .apply("FIND_IN_SET({0},ancestors_p_id)", wbsTreeContract.getPKeyId());
-                    wrapperContract.apply("FIND_IN_SET({0},ancestors_p_id)", wbsTreeContract.getPKeyId());
+                    if(CollectionUtil.isNotEmpty(formList)){
+                        wrapperContract.in(WbsTreeContract::getIsTypePrivatePid, formList);
+                    }
 
                     //当前合同的子节点数据
                     List<WbsTreeContract> wbsTreeContracts = wbsTreeContractMapper.selectList(wrapperContract);
                     wbsTreeContracts.add(wbsTreeContract);
 
+
+                    //获取所有表单的父节点
+                    List<Long> pIds = wbsTreeContracts.stream().filter(f -> f.getType() == 2).map(WbsTreeContract::getPId).collect(Collectors.toList());
                     HashMap<Long, Integer> informationQueryMap = new HashMap<>();
-                    //查询质检合同节点填表信息
-                    String sql = "SELECT  b.wbs_id, b.STATUS  FROM" +
-                            "( SELECT p_key_id FROM m_wbs_tree_contract WHERE is_deleted = 0 AND contract_id = " + contractInfo.getId() + " AND FIND_IN_SET( " + wbsTreeContract.getPKeyId() + ", ancestors_p_id ) ) a" +
-                            " INNER JOIN (  SELECT  c.wbs_id, c.STATUS FROM u_information_query c" +
-                            " JOIN ( SELECT wbs_id, MAX( update_time ) AS max_update_time FROM u_information_query WHERE contract_id = " + contractInfo.getId() + " GROUP BY wbs_id ) subquery ON c.wbs_id = subquery.wbs_id " +
-                            " AND c.update_time = subquery.max_update_time WHERE  c.contract_id = " + contractInfo.getId() + " and c.is_deleted = 0 ) b ON a.p_key_id = b.wbs_id";
-
-                    List<InformationQuery> informationQueries = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(InformationQuery.class));
-                    //质检节点填报信息
-
-                    for (InformationQuery informationQuery : informationQueries) {
-                        informationQueryMap.put(informationQuery.getWbsId(), informationQuery.getStatus());
+                    if(CollectionUtil.isNotEmpty(pIds)){
+                        //查询质检合同节点填表信息
+                        String sql = "SELECT  c.wbs_id, c.STATUS FROM u_information_query c" +
+                                " JOIN ( SELECT wbs_id, MAX( update_time ) AS max_update_time FROM u_information_query WHERE contract_id = " + contractInfo.getId() + " GROUP BY wbs_id ) subquery ON c.wbs_id = subquery.wbs_id " +
+                                " AND c.update_time = subquery.max_update_time WHERE  c.contract_id = " + contractInfo.getId() + " and c.is_deleted = 0  and c.wbs_id in(" + StringUtil.join(pIds,",") +")";
+
+                        List<InformationQuery> informationQueries = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(InformationQuery.class));
+                        //质检节点填报信息
+
+                        for (InformationQuery informationQuery : informationQueries) {
+                            informationQueryMap.put(informationQuery.getWbsId(), informationQuery.getStatus());
+                        }
                     }
 
 
+
+
+
                     //合同段节点对应的项目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)) {
-                        //二次筛选  只保留任务选中的表单   但可能新增的数据包含新节点
-                        addPrivateNodes = addPrivateNodes.stream().filter(f -> formList.contains(f.getPKeyId())).collect(Collectors.toList());
-                    }
-
                     //筛选出需要更新的节点  同时做数据隔离
                     List<WbsTreeContract> editContractNodes = new ArrayList<>();
                     for (WbsTreePrivate templateNode : wbsTreePrivates) {
                         //更新只跟新表单
                         for (WbsTreeContract editContractNode : wbsTreeContracts) {
 
+
                             // 判断模板表与项目表 html是否一致
                             if (ObjectUtils.equals(templateNode.getPKeyId(), editContractNode.getIsTypePrivatePid())) {
                                 if (templateNode.getType() == 2) {
                                     //同步范围 当前节点是否允许修改
                                     Boolean isSync = false;
+
+                                    //是否记录历史html
+                                    Boolean isOldHtml = false;
+
                                     if (CollectionUtil.isNotEmpty(contractRanges)) {
                                         switch (Integer.valueOf(wbsTreePrivate.getWbsType())) {
                                             //质检
                                             case 1:
-                                                Integer submit = informationQueryMap.get(editContractNode.getPKeyId());
+                                                //判断是否已保存
+                                                if(StringUtils.isEmpty(editContractNode.getInitTableName())){
+                                                    throw new ServiceException(editContractNode.getNodeName() + "( " + editContractNode.getPKeyId() + ")--实体表不存在");
+                                                }
+                                                String isSave = "select count(0) from " + editContractNode.getInitTableName() + " where p_key_id = " + editContractNode.getPKeyId();
+                                                Integer i = jdbcTemplate.queryForObject(isSave, Integer.class);
+
+                                                //判断是否上报审批
+                                                Integer submit = informationQueryMap.get(editContractNode.getPId());
 
                                                 if (submit == null && contractRanges.contains(WbsSyncTypeEnum.NOT_FILLED_IN.code)) {
-                                                    //未审批 101
+                                                    //未填报 101
                                                     isSync = true;
                                                 } else {
-                                                    if (submit == null) {
-                                                        throw new ServiceException(wbsTreeContract.getNodeName() + "--下表单未查到填报信息");
+                                                    if (i == 0) {
+                                                        isSync = false;
+                                                        errorMsg.append(editContractNode.getNodeName() + "(" + editContractNode.getPKeyId() + ")").append("未填报;");
                                                     }
-                                                    if (submit == 0 && contractRanges.contains(WbsSyncTypeEnum.ALREADY_FILLED_IN_NOT_REPORTED.code)) {
+                                                    if (contractRanges.contains(WbsSyncTypeEnum.ALREADY_FILLED_IN_NOT_REPORTED.code) && (submit == null || submit == 0)) {
                                                         //已填报-未上报 102
                                                         isSync = true;
-                                                    } else if (submit == 1 && contractRanges.contains(WbsSyncTypeEnum.PENDING_APPROVAL.code)) {
-                                                        //待审批 104
-                                                        isSync = true;
-                                                    } else if (submit == 2 && contractRanges.contains(WbsSyncTypeEnum.APPROVED.code)) {
-                                                        //已审批 105
-                                                        isSync = true;
+                                                    } else {
+                                                        if (submit == null) {
+                                                            isSync = false;
+                                                            errorMsg.append(editContractNode.getNodeName() + "(" + editContractNode.getPKeyId() + ")").append("未上报;");
+                                                        }else{
+                                                            if (submit == 1 && contractRanges.contains(WbsSyncTypeEnum.PENDING_APPROVAL.code)) {
+                                                                //待审批 104
+                                                                isSync = true;
+                                                                isOldHtml = true;
+                                                            } else if (submit == 2 && contractRanges.contains(WbsSyncTypeEnum.APPROVED.code)) {
+                                                                //已审批 105
+                                                                isSync = true;
+                                                                isOldHtml = true;
+                                                            }
+                                                        }
                                                     }
                                                 }
                                                 break;
@@ -734,6 +765,10 @@ public class WbsSynchronousServiceImpl {
 
                                     //清表 公式 元素
                                     if (collect.contains(2) || collect.contains(3) || collect.contains(5)) {
+                                        if(collect.contains(2) && (templateNode.getHtmlUrl() == null || templateNode.getInitTableName() == null || templateNode.getExcelId() == null)){
+                                            errorMsg.append(editContractNode.getNodeName() + "(" + editContractNode.getPKeyId() + ")").append("未配置清表;");
+                                            continue;
+                                        }
                                         editContractNode.setExcelId(templateNode.getExcelId());
                                         editContractNode.setInitTableName(templateNode.getInitTableName());
                                         editContractNode.setHtmlUrl(templateNode.getHtmlUrl());
@@ -747,21 +782,15 @@ public class WbsSynchronousServiceImpl {
                                     }
 
                                     //手动选中的表单 进行筛选
-                                    if (CollectionUtil.isNotEmpty(formList)) {
-                                        if (formList.contains(templateNode.getPKeyId())) {
-                                            if (collect.contains(2) || collect.contains(3) || collect.contains(4) || collect.contains(5) || collect.contains(6) || collect.contains(7)) {
-                                                if (isSync) {
-                                                    editContractNodes.add(editContractNode);
-                                                }
-
-                                            }
+                                    if (collect.contains(2) || collect.contains(3) || collect.contains(4) || collect.contains(5) || collect.contains(6) || collect.contains(7)) {
+                                        if (isSync) {
+                                            editContractNodes.add(editContractNode);
                                         }
-                                    } else {
-                                        if (collect.contains(2) || collect.contains(3) || collect.contains(4) || collect.contains(5) || collect.contains(6) || collect.contains(7)) {
-                                            if (isSync) {
-                                                editContractNodes.add(editContractNode);
-                                            }
+                                        //记录历史html
+                                        if (isOldHtml) {
+                                            oldHtml.add(editContractNode);
                                         }
+
                                     }
                                     //找到了某个选中节点下与项目节点想同的节点了  提前结束循环,节省资源
                                     break;
@@ -769,10 +798,6 @@ public class WbsSynchronousServiceImpl {
                             }
                         }
                     }
-                    //修改数据二次筛选  只保留任务选中的表单   但可能新增的数据包含新节点
-                    if (CollectionUtil.isNotEmpty(formList)) {
-                        editContractNodes = editContractNodes.stream().filter(f -> formList.contains(f.getPKeyId())).collect(Collectors.toList());
-                    }
 
                     //合同段新增节点
                     List<WbsTreeContract> addContractNode = null;
@@ -818,20 +843,24 @@ public class WbsSynchronousServiceImpl {
         Set<Long> pIds = collect1.keySet();
         Integer nodeNumEnd = 0;
 
+        Map<Long, List<WbsTreeContract>> collect2 = oldHtml.stream().collect(Collectors.groupingBy(WbsTreeContract::getPId));
+
         for (Long pId : pIds) {
             nodeNumEnd++;
             List<WbsTreeContract> list = collect1.get(pId);
+            List<WbsTreeContract> oldHtmlPId = collect2.get(pId);
 
-            wbsSynchronousEViSaService.updateContract(wbsTreeSynchronousRecord.getType(), pId, wbsTreeSynchronousRecord.getCreateUserId(), list);
+            wbsSynchronousEViSaService.updateContract(wbsTreeSynchronousRecord.getType(), pId, wbsTreeSynchronousRecord.getCreateUserId(), list, oldHtmlPId);
             synchronousRecordMapper.update(null, Wrappers.<WbsTreeSynchronousRecord>lambdaUpdate()
                     .set(WbsTreeSynchronousRecord::getNodeNumEnd, nodeNumEnd)
                     .set(WbsTreeSynchronousRecord::getUpdateTime, DateTime.now())
                     .eq(WbsTreeSynchronousRecord::getId, wbsTreeSynchronousRecord.getId()));
         }
 
+
         synchronousRecordMapper.update(null, Wrappers.<WbsTreeSynchronousRecord>lambdaUpdate()
                 .set(WbsTreeSynchronousRecord::getStatus, 2)
-                .set(WbsTreeSynchronousRecord::getErrorMsg, null)
+                .set(WbsTreeSynchronousRecord::getErrorMsg, errorMsg.toString())
                 .set(WbsTreeSynchronousRecord::getUpdateTime, DateTime.now())
                 .eq(WbsTreeSynchronousRecord::getId, wbsTreeSynchronousRecord.getId()));
     }
@@ -867,7 +896,7 @@ public class WbsSynchronousServiceImpl {
                 }
                 //如果现在还找不到当前节点的父节点就表示数据有问题
                 if (addPrivateParentNodes.isEmpty()) {
-                    throw new ServiceException(addPrivateNode.getNodeName() + "-找不到父节点");
+                    throw new ServiceException(addPrivateNode.getNodeName()  + "( " + addPrivateNode.getPKeyId() + ")-找不到父节点");
                 }
                 //当前新增节点的父节点
                 WbsTreePrivate parent = addPrivateParentNodes.get(0);
@@ -902,7 +931,7 @@ public class WbsSynchronousServiceImpl {
                             File file_in = ResourceUtil.getFile(tree.getHtmlUrl());
                             if (!file_in.exists() || file_in.length() == 0) {
                                 //如果本地服务器上没有
-                                throw new ServiceException(tree.getNodeName() + "HTML文件不存在");
+                                throw new ServiceException(tree.getNodeName() + "( " + tree.getPKeyId() + ")HTML文件不存在");
                             }
                             File file_out = ResourceUtil.getFile(htmlUrl);
                             //查询父级文件夹
@@ -1041,7 +1070,7 @@ public class WbsSynchronousServiceImpl {
                 //如果现在还找不到当前节点的父节点就表示数据有问题
                 if (addContractParentNodes.isEmpty()) {
                     //TODO
-                    throw new ServiceException(addContractNode.getNodeName() + "-找不到父节点");
+                    throw new ServiceException(addContractNode.getNodeName()  + "( " + addContractNode.getPKeyId() + ")-找不到父节点");
                 }
                 //当前新增节点的父节点
                 WbsTreeContract parent = addContractParentNodes.get(0);

+ 22 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsTreeContractOldHtmlServiceImpl.java

@@ -0,0 +1,22 @@
+package org.springblade.manager.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springblade.manager.entity.WbsTreeContractOldHtml;
+import org.springblade.manager.service.WbsTreeContractOldHtmlService;
+import org.springblade.manager.mapper.WbsTreeContractOldHtmlMapper;
+import org.springframework.stereotype.Service;
+
+/**
+* @author LHB
+* @description 针对表【m_wbs_tree_contract_old_html(合同段表单上报之前的html记录)】的数据库操作Service实现
+* @createDate 2025-07-01 17:37:36
+*/
+@Service
+public class WbsTreeContractOldHtmlServiceImpl extends ServiceImpl<WbsTreeContractOldHtmlMapper, WbsTreeContractOldHtml>
+    implements WbsTreeContractOldHtmlService {
+
+}
+
+
+
+

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

@@ -4261,6 +4261,10 @@ public static boolean hasConflictingCodes(List<ImportTreeDto> list) {
         return false;
     }
 
-
-
+    @Override
+    public List<WbsTreeContract> queryListByPIds(List<Long> pIds) {
+        return baseMapper.selectList(Wrappers.<WbsTreeContract>lambdaQuery()
+                .in(WbsTreeContract::getPId, pIds)
+                .eq(WbsTreeContract::getIsDeleted, 0));
+    }
 }

+ 41 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/utils/FileUtils.java

@@ -29,6 +29,7 @@ import org.springblade.common.utils.CommonUtil;
 import org.springblade.common.utils.SnowFlakeUtil;
 import org.springblade.common.utils.SystemUtils;
 import org.springblade.common.vo.DataVO;
+import org.springblade.core.log.exception.ServiceException;
 import org.springblade.core.tool.utils.FileUtil;
 import org.springblade.core.tool.utils.Func;
 import org.springblade.core.tool.utils.IoUtil;
@@ -42,6 +43,7 @@ import java.awt.*;
 import java.awt.image.BufferedImage;
 import java.io.*;
 import java.net.URLEncoder;
+import java.nio.file.*;
 import java.util.List;
 import java.util.*;
 import java.util.regex.Matcher;
@@ -1052,6 +1054,45 @@ public class FileUtils {
     }
 
 
+    /**
+     * 获取本地文件,若不存在则从URL下载
+     */
+    public static Path getOrDownloadFile(String localPath) throws IOException {
+        Path path = Paths.get(localPath);
+
+        // 如果本地文件不存在,则从URL下载
+        if (!Files.exists(path)) {
+            System.out.println("本地文件不存在,开始下载...");
+            // 确保父目录存在
+            Path parentDir = path.getParent();
+            if (parentDir != null && !Files.exists(parentDir)) {
+                Files.createDirectories(parentDir);
+            }
+
+            // 从URL下载文件
+            try (InputStream in = CommonUtil.getOSSInputStream(getNetUrl(localPath))) {
+                Files.copy(in, path);
+                System.out.println("文件下载成功: " + path);
+            } catch (IOException e) {
+                throw new IOException("下载文件失败: " + e.getMessage(), e);
+            }
+        }
+        return path;
+    }
+
+    /**
+     * 生成带后缀的副本路径
+     */
+    public static Path generateCopyPath(Path originalPath) {
+        String suffix = "_copy";
+        String fileName = originalPath.getFileName().toString();
+        int dotIndex = fileName.lastIndexOf('.');
 
+        // 处理带扩展名和不带扩展名的文件
+        String newName = (dotIndex > 0)
+                ? fileName.substring(0, dotIndex) + suffix + fileName.substring(dotIndex)
+                : fileName + suffix;
 
+        return originalPath.resolveSibling(newName);
+    }
 }