Преглед изворни кода

Merge branch 'dev' of http://219.151.181.73:3000/zhuwei/bladex into dev

laibulaizheli пре 2 недеља
родитељ
комит
3bb6820679
30 измењених фајлова са 1274 додато и 70 уклоњено
  1. 14 0
      blade-service-api/blade-archive-api/src/main/java/org/springblade/archive/dto/ScanFileMoveDTO.java
  2. 52 0
      blade-service-api/blade-archive-api/src/main/java/org/springblade/archive/entity/ScanFile.java
  3. 35 0
      blade-service-api/blade-archive-api/src/main/java/org/springblade/archive/entity/ScanFolder.java
  4. 18 0
      blade-service-api/blade-archive-api/src/main/java/org/springblade/archive/vo/ScanFolderVO.java
  5. 3 0
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/feign/ArchiveFileClient.java
  6. 41 0
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/vo/ChekPdfVo.java
  7. 3 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/entity/ContractInfo.java
  8. 21 18
      blade-service/blade-archive/src/main/java/org/springblade/archive/controller/ArchivesAutoController.java
  9. 178 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/controller/ScanFileController.java
  10. 26 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/ScanFileMapper.java
  11. 30 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/ScanFileMapper.xml
  12. 15 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/ScanFolderMapper.java
  13. 12 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/ScanFolderMapper.xml
  14. 2 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/service/IArchivesAutoService.java
  15. 31 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/service/ScanFileService.java
  16. 5 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/service/ScanFolderService.java
  17. 13 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ArchivesAutoServiceImpl.java
  18. 520 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ScanFileServiceImpl.java
  19. 16 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ScanFolderServiceImpl.java
  20. 18 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/utils/FileUtils.java
  21. 3 4
      blade-service/blade-business/src/main/java/org/springblade/business/controller/InformationWriteQueryController.java
  22. 5 0
      blade-service/blade-business/src/main/java/org/springblade/business/feignClient/ArchiveFileClientImpl.java
  23. 8 2
      blade-service/blade-business/src/main/java/org/springblade/business/mapper/ArchiveFileMapper.xml
  24. 15 3
      blade-service/blade-business/src/main/java/org/springblade/business/service/impl/ArchiveFileServiceImpl.java
  25. 2 0
      blade-service/blade-business/src/main/java/org/springblade/business/service/impl/InformationQueryServiceImpl.java
  26. 1 0
      blade-service/blade-business/src/main/java/org/springblade/business/service/impl/TrialSelfInspectionRecordServiceImpl.java
  27. 6 3
      blade-service/blade-e-visa/src/main/java/org/springblade/evisa/controller/ChekSignData.java
  28. 4 4
      blade-service/blade-e-visa/src/main/java/org/springblade/evisa/service/impl/EVDataServiceImpl.java
  29. 167 34
      blade-service/blade-e-visa/src/main/java/org/springblade/evisa/service/impl/ScrDataServiceImpl.java
  30. 10 2
      blade-service/blade-e-visa/src/main/java/org/springblade/evisa/utils/PdfAddimgUtil.java

+ 14 - 0
blade-service-api/blade-archive-api/src/main/java/org/springblade/archive/dto/ScanFileMoveDTO.java

@@ -0,0 +1,14 @@
+package org.springblade.archive.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ScanFileMoveDTO {
+   private List<Long> ids;
+   private Long nodeId;
+}

+ 52 - 0
blade-service-api/blade-archive-api/src/main/java/org/springblade/archive/entity/ScanFile.java

@@ -0,0 +1,52 @@
+package org.springblade.archive.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.omg.CORBA.PRIVATE_MEMBER;
+
+@TableName(value = "scan_file")
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ScanFile {
+    @TableId(
+            value = "id",
+            type = IdType.ASSIGN_ID
+    )
+    private Long id;
+    @ApiModelProperty(value = "项目id")
+    private Long projectId;
+    @ApiModelProperty(value = "合同id")
+    private Long contractId;
+    @ApiModelProperty(value = "文件夹id")
+    private Long folderId;
+    @ApiModelProperty(value = "序号")
+    private Integer sort;
+    @ApiModelProperty(value = "数字编号")
+    private Integer digitalNum;
+    @ApiModelProperty(value = "文件编号")
+    private String fileNum;
+    @ApiModelProperty(value = "文件题名")
+    private String fileName;
+    @ApiModelProperty(value = "文件页数")
+    private String fileSize;
+    @ApiModelProperty(value = "文件日期")
+    private String fileDate;
+    @ApiModelProperty(value = "创建时间")
+    private String creatTime;
+    @ApiModelProperty(value = "文件路径")
+    private String filePath;
+    @ApiModelProperty(value = "OSS路径")
+    private String ossUrl;
+    @ApiModelProperty(value = "负责人")
+    private String responsible;
+    @ApiModelProperty(value = "是否删除")
+    private Integer isDeleted;
+    @ApiModelProperty(value = "是否移动")
+    private Integer isMove;
+}

+ 35 - 0
blade-service-api/blade-archive-api/src/main/java/org/springblade/archive/entity/ScanFolder.java

@@ -0,0 +1,35 @@
+package org.springblade.archive.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@TableName("scan_folder")
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ScanFolder {
+    @TableId(
+            value = "id",
+            type = IdType.ASSIGN_ID
+    )
+    @ApiModelProperty(value = "id")
+    private Long id;
+    @ApiModelProperty(value = "父级id")
+    private Long parentId;
+    @ApiModelProperty(value = "项目id")
+    private Long projectId;
+    @ApiModelProperty(value = "合同id")
+    private Long contractId;
+    @ApiModelProperty(value = "文件夹名称")
+    private String folderName;
+    @ApiModelProperty(value = "文件夹路径")
+    private String folderPath;
+    @ApiModelProperty(value = "是否删除")
+    private Integer isDeleted;
+}

+ 18 - 0
blade-service-api/blade-archive-api/src/main/java/org/springblade/archive/vo/ScanFolderVO.java

@@ -0,0 +1,18 @@
+package org.springblade.archive.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springblade.archive.entity.ScanFolder;
+
+import java.util.List;
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ScanFolderVO extends ScanFolder {
+    @ApiModelProperty(value = "是否有子级")
+    private Boolean hasChildren;
+    @ApiModelProperty(value = "子级节点")
+    private List<ScanFolderVO> childs;
+}

+ 3 - 0
blade-service-api/blade-business-api/src/main/java/org/springblade/business/feign/ArchiveFileClient.java

@@ -175,4 +175,7 @@ public interface ArchiveFileClient {
 
     @PostMapping(API_PREFIX + "/getAllArchiveFileByIds")
     List<ArchiveFile> getAllArchiveFileByIds(@RequestBody List<String> strList);
+
+    @PostMapping(API_PREFIX + "/saveBatchArchiveFile")
+    void saveBatchArchiveFile(@RequestBody List<ArchiveFile> list);
 }

+ 41 - 0
blade-service-api/blade-business-api/src/main/java/org/springblade/business/vo/ChekPdfVo.java

@@ -0,0 +1,41 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.business.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 视图实体类
+ *
+ * @author BladeX
+ * @since 2022-07-08
+ */
+@Data
+public class ChekPdfVo {
+    private static final long serialVersionUID = 1L;
+
+
+    @ApiModelProperty("1:节点添加 2:文件添加")
+    private String type;
+
+    @ApiModelProperty("ids")
+    private String ids;
+
+    @ApiModelProperty("1施工2监理")
+    private String classify;
+}

+ 3 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/entity/ContractInfo.java

@@ -181,6 +181,9 @@ public class ContractInfo extends BaseEntity {
     @ApiModelProperty(value = "卷盒规格")
     private String specification;
 
+    @ApiModelProperty(value = "1正在扫描中 ,2没有扫描")
+    private Integer isScan;
+
     public BigDecimal getProjectMileage() {
         if (projectMileage == null){
             return null;

+ 21 - 18
blade-service/blade-archive/src/main/java/org/springblade/archive/controller/ArchivesAutoController.java

@@ -651,21 +651,15 @@ public class ArchivesAutoController extends BladeController {
 	@ApiOperationSupport(order = 13)
 	@ApiOperation(value = "重新生成案卷", notes = "传入ids")
 	@Transactional
-	public R reCreateArchiveAuto(@ApiParam(value = "主键集合", required = true) @RequestParam String ids,@ApiParam(value = "合并后的文件题目", required = true)String name){
-		//先查出勾选的案卷
-		List<ArchivesAuto> archivesAutoList=archivesAutoService.listByIds(Func.toLongList(ids));
-//		if(archivesAutoList.size()<=1){
-//			return R.fail("请选择多个案卷进行合并");
-//		}
-		//查出所有案卷文件
-		List<ArchiveFile>archiveFileList=new ArrayList<>();
-		List<Long> longList = Func.toLongList(ids);
-		for (Long id : longList) {
-			archiveFileList.addAll(archiveFileClient.getArchiveFileByArchiveIds(id+""));
-		}
-		//根据档号后缀排序 拿到第一个
-		ArchivesAuto auto = archivesAutoList.get(0);
-		if(longList.size()>1){
+	public R reCreateArchiveAuto(@ApiParam(value = "主键集合", required = true) @RequestParam String ids,@ApiParam(value = "合并后的文件题目", required = true)String name,Integer type){
+		if(type==1){
+			//先查出勾选的案卷
+			List<ArchivesAuto> archivesAutoList=archivesAutoService.listByIds(Func.toLongList(ids));
+			if(archivesAutoList.size()<=1){
+				return R.fail("请选择多个案卷进行合并");
+			}
+			//根据档号后缀排序 拿到第一个
+			ArchivesAuto auto = archivesAutoList.get(0);
 			archivesAutoList.sort(Comparator.comparingInt(a -> {
 				String fileNumber = a.getFileNumber();
 				if (fileNumber == null || fileNumber.isEmpty()) {
@@ -690,6 +684,12 @@ public class ArchivesAutoController extends BladeController {
 			}));
 			//将除第一个以外的案卷文件archiveId 设置成第一个的id
 			List<ArchiveFile>updateArchiveFileList=new ArrayList<>();
+			//查出所有案卷文件
+			List<ArchiveFile>archiveFileList=new ArrayList<>();
+			List<Long> longList = Func.toLongList(ids);
+			for (Long id : longList) {
+				archiveFileList.addAll(archiveFileClient.getArchiveFileByArchiveIds(id+""));
+			}
 			int i=1;
 			for (ArchiveFile file : archiveFileList) {
 				if (!file.getArchiveId().equals(auto.getId())) {
@@ -703,10 +703,13 @@ public class ArchivesAutoController extends BladeController {
 			//删除其他案卷
 			archivesAutoList.remove(auto);
 			archivesAutoService.deleteLogic(archivesAutoList.stream().map(o->o.getId()).collect(Collectors.toList()));
+			//设置案卷页码和四要素
+			archivesAutoService.reCreateArchiveAuto(auto, archiveFileList);
+			return R.status(true);
+		}else {
+			archivesAutoService.reCreateArchiveAuto1(ids);
+			return R.success("正在重组,请稍后查看");
 		}
-		//设置案卷页码和四要素
-		archivesAutoService.reCreateArchiveAuto(auto, archiveFileList);
-		return R.status(true);
 	}
 
 	@PostMapping("/creatFileNameFormAI")

+ 178 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/controller/ScanFileController.java

@@ -0,0 +1,178 @@
+package org.springblade.archive.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import org.springblade.archive.dto.ScanFileMoveDTO;
+import org.springblade.archive.entity.ScanFile;
+import org.springblade.archive.entity.ScanFolder;
+import org.springblade.archive.service.ScanFileService;
+import org.springblade.archive.service.ScanFolderService;
+import org.springblade.archive.vo.ScanFolderVO;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.tool.api.R;
+import org.springblade.manager.entity.ContractInfo;
+import org.springblade.meter.vo.InterimPayCertificateVO;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ThreadPoolExecutor;
+
+import java.util.ArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+@RestController
+@AllArgsConstructor
+@RequestMapping("/scanFile")
+@Api(value = "扫描文件接口", tags = "扫描文件接口")
+public class ScanFileController {
+    private final ScanFileService scanFileService;
+    private final JdbcTemplate jdbcTemplate;
+
+    // 线程池
+    @Resource(name = "taskExecutor1")
+    private ThreadPoolExecutor executor;
+
+
+    @GetMapping("/startOrEndScan")
+    @ApiOperation("开始或结束扫描")
+    @ApiImplicitParams({@ApiImplicitParam(name = "contractId", value = "合同ID"),@ApiImplicitParam(name = "type", value = "1:开始扫描 0:结束扫描")})
+    public R startOrEndScan(Long contractId,Integer type){
+        String sql="update m_contract_info set is_scan="+type+" where id="+contractId;
+        jdbcTemplate.update(sql);
+        return R.success("操作成功"+ (type==1?",正在扫描中":",已结束扫描"));
+    }
+
+    @Scheduled(fixedDelay = 120000)
+    public void scan (){
+        String sql = "select * from m_contract_info where is_scan=1 and is_deleted=0";
+        List<ContractInfo> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(ContractInfo.class));
+        if (!list.isEmpty()) {
+            // 创建线程池
+            int threadCount = Math.min(list.size(), Runtime.getRuntime().availableProcessors() * 2);
+            ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
+            try {
+                // 存储Future对象以获取执行结果
+                List<Future<?>> futures = new ArrayList<>();
+                // 提交任务到线程池
+                for (ContractInfo contractInfo : list) {
+                    Future<?> future = executorService.submit(() -> {
+                        try {
+                            scanFileService.scanAndSave(contractInfo.getId(), Long.parseLong(contractInfo.getPId()));
+                            System.out.println("合同ID " + contractInfo.getId() + " 扫描完成");
+                        } catch (Exception e) {
+                            System.err.println("处理合同ID " + contractInfo.getId() + " 时出错: " + e.getMessage());
+                            throw e; // 重新抛出异常以便Future能捕获
+                        }
+                    });
+                    futures.add(future);
+                }
+                // 关闭线程池
+                executorService.shutdown();
+                // 等待所有任务完成并检查结果
+                for (int i = 0; i < futures.size(); i++) {
+                    try {
+                        futures.get(i).get(); // 这会阻塞直到任务完成
+                        System.out.println("任务 " + i + " 已成功完成");
+                    } catch (Exception e) {
+                        System.err.println("任务 " + i + " 执行失败: " + e.getMessage());
+                    }
+                }
+                // 等待线程池完全终止
+                if (executorService.awaitTermination(30, TimeUnit.MINUTES)) {
+                    System.out.println("所有扫描任务已完成");
+                } else {
+                    System.err.println("部分扫描任务超时未完成");
+                    executorService.shutdownNow();
+                }
+
+            } catch (InterruptedException e) {
+                System.err.println("线程执行被中断: " + e.getMessage());
+                executorService.shutdownNow();
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    @GetMapping("/getScanFolder")
+    @ApiOperation("获取扫描文件夹")
+    @ApiImplicitParams({@ApiImplicitParam(name = "contractId", value = "合同ID"),@ApiImplicitParam(name = "projectId", value = "项目ID")})
+    public R<List<ScanFolderVO>> getScanFolder(Long contractId, Long projectId){
+        List<ScanFolderVO> list = scanFileService.getScanFolder(contractId,projectId);
+        return R.data( list);
+    }
+
+    @GetMapping("/getScanFile")
+    @ApiOperation("获取扫描文件")
+    @ApiImplicitParams({@ApiImplicitParam(name = "contractId", value = "合同ID"),@ApiImplicitParam(name = "projectId", value = "项目ID"),@ApiImplicitParam(name = "folderId", value = "文件夹ID"),@ApiImplicitParam(name = "query", value = "查询参数")})
+    public R<IPage<ScanFile>> getScanFile(Long contractId, Long projectId, Long folderId, Query query,Integer move){
+        IPage<ScanFile> page=scanFileService.getScanFile(contractId,projectId,folderId,query,move);
+        return R.data(page);
+    }
+    @GetMapping("/getDetil")
+    @ApiOperation("获取扫描文件详情")
+    @ApiImplicitParam(name = "id", value = "文件ID")
+    public R<ScanFile> getDetil(Long id){
+        return R.data(scanFileService.getById(id));
+    }
+    @PostMapping("/updateScanFile")
+    @ApiOperation("更新扫描文件")
+    @ApiImplicitParam(name = "scanFiles", value = "扫描文件")
+    public R updateScanFile(@RequestBody List<ScanFile> scanFiles){
+        return R.data(scanFileService.updateBatchById(scanFiles));
+    }
+
+    @GetMapping("/deleteScanFile")
+    @ApiOperation("删除扫描文件")
+    @ApiImplicitParam(name = "fileNames", value = "文件名称")
+    public R deleteScanFile(String ids){
+        return R.success(scanFileService.deleteScanFile(ids));
+    }
+
+    @GetMapping("/getContractStatus")
+    @ApiOperation("获取合同状态")
+    @ApiImplicitParam(name = "contractId", value = "合同ID")
+    public R<Integer> getContractStatus(Long contractId){
+        if (contractId == null) {
+            return R.data(2);
+        }
+        String sql = "SELECT is_scan FROM m_contract_info WHERE id = ?";
+        try {
+            Integer result = jdbcTemplate.queryForObject(sql, Integer.class, contractId);
+            return R.data(result != null ? result : 2);
+        } catch (Exception e) {
+            // 记录异常日志(可选)
+            System.err.println("查询合同扫描状态出错: " + e.getMessage());
+            return R.data(2);
+        }
+    }
+
+    @PostMapping("/autoRecognize")
+    @ApiOperation("自动识别")
+    @ApiImplicitParam(name = "ids", value = "文件IDs")
+    public R autoRecognize(String ids){
+       return R.status(scanFileService.autoRecognize(ids));
+    }
+
+    @PostMapping("/moveScanFile")
+    @ApiOperation("移动文件")
+    @ApiImplicitParams({@ApiImplicitParam(name = "ids", value = "文件IDs"),@ApiImplicitParam(name = "nodeId", value = "目标文件夹ID") })
+    public R moveScanFile(@RequestBody ScanFileMoveDTO dto){
+        return R.status(scanFileService.moveScanFile(dto.getIds(),dto.getNodeId()));
+    }
+
+
+
+}

+ 26 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/ScanFileMapper.java

@@ -0,0 +1,26 @@
+package org.springblade.archive.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import feign.Param;
+import org.springblade.archive.entity.ScanFile;
+import org.springblade.archive.entity.ScanFolder;
+
+import java.util.List;
+
+public interface ScanFileMapper extends BaseMapper<ScanFile> {
+
+    Integer exists(@Param("projectId") Long projectId, @Param("contractId") Long contractId, @Param("fileName") String fileName);
+
+    int insert(ScanFile scanFile);
+
+    Integer selectMaxDigitalNum(@Param("contractId") Long contractId, @Param("projectId") Long projectId);
+
+    Integer selectMaxSort(@Param("contractId") Long contractId, @Param("projectId") Long projectId, @Param("folderId") Long folderId);
+
+    List<ScanFolder> getScanFolder(@Param("contractId") Long contractId, @Param("projectId") Long projectId);
+
+    IPage<ScanFile> getScanFile(IPage<ScanFile> page, @Param("contractId") Long contractId, @Param("projectId") Long projectId, @Param("folderId") Long folderId,@Param("move")Integer  move);
+
+    void removeScan(@Param("longList") List<Long> longList);
+}

+ 30 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/ScanFileMapper.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.archive.mapper.ScanFileMapper">
+    <update id="removeScan">
+        update scan_file set is_deleted = 1 where project_id = #{projectId} and contract_id = #{contractId}
+        and id in
+        <foreach collection="longList" item="id" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </update>
+    <select id="exists" resultType="java.lang.Integer">
+        select COUNT(*) FROM scan_file WHERE project_id = #{projectId} AND contract_id = #{contractId}  AND file_name = #{fileName}
+    </select>
+    <select id="selectMaxDigitalNum" resultType="java.lang.Integer">
+        select MAX(digital_num) FROM scan_file WHERE project_id = #{projectId} AND contract_id = #{contractId}
+    </select>
+    <select id="selectMaxSort" resultType="java.lang.Integer">
+        select MAX(sort) FROM scan_file WHERE project_id = #{projectId} AND contract_id = #{contractId} And folder_id = #{folderId}
+    </select>
+    <select id="getScanFolder" resultType="org.springblade.archive.entity.ScanFolder">
+        select * from scan_folder where project_id = #{projectId} and contract_id = #{contractId} and is_deleted = 0
+    </select>
+    <select id="getScanFile" resultType="org.springblade.archive.entity.ScanFile">
+        select * from scan_file where project_id = #{projectId} and contract_id = #{contractId} and folder_id = #{folderId} and is_deleted = 0
+        <if test="move!= null">
+            and is_move = #{move}
+        </if>
+        order by digital_num
+    </select>
+</mapper>

+ 15 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/ScanFolderMapper.java

@@ -0,0 +1,15 @@
+package org.springblade.archive.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
+import org.springblade.archive.entity.ScanFolder;
+
+public interface ScanFolderMapper extends BaseMapper<ScanFolder> {
+
+    int exists(@Param("projectId") Long projectId, @Param("contractId") Long contractId, @Param("folderName") String folderName);
+
+    int insert(ScanFolder scanFolder);
+
+
+    Long getId(@Param("folderName") String folderName, @Param("contractId") Long contractId, @Param("projectId") Long projectId);
+}

+ 12 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/ScanFolderMapper.xml

@@ -0,0 +1,12 @@
+<?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.archive.mapper.ScanFolderMapper">
+
+
+    <select id="exists" resultType="java.lang.Integer">
+        select COUNT(*) FROM scan_folder WHERE project_id = #{projectId} AND contract_id = #{contractId} AND folder_name = #{folderName}
+    </select>
+    <select id="getId" resultType="java.lang.Long">
+        select id from scan_folder where project_id = #{projectId} AND contract_id = #{contractId} AND folder_name = #{folderName}
+    </select>
+</mapper>

+ 2 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/service/IArchivesAutoService.java

@@ -185,4 +185,6 @@ public interface IArchivesAutoService extends BaseService<ArchivesAuto> {
 	List<Long> getArchiveIdsByNodes(List<Long> ids);
 
 	void reomoveArchiveAndFile(List<Long> archiveIds);
+
+    void reCreateArchiveAuto1(String ids);
 }

+ 31 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/service/ScanFileService.java

@@ -0,0 +1,31 @@
+package org.springblade.archive.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.springblade.archive.entity.ScanFile;
+import org.springblade.archive.vo.ScanFolderVO;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.tool.api.R;
+
+import java.util.List;
+
+
+public interface ScanFileService extends IService<ScanFile> {
+    R scanAndSaveFolder(Long contractId, Long projectId);
+
+    R scanAndSaveFiles(Long contractId, Long projectId);
+
+    void sortNumber(Long contractId, Long projectId);
+
+    void scanAndSave(Long contractId,Long projectId);
+
+    List<ScanFolderVO> getScanFolder(Long contractId, Long projectId);
+
+    IPage<ScanFile> getScanFile(Long contractId, Long projectId, Long folderId, Query query,Integer move);
+
+    String deleteScanFile(String ids);
+
+    Boolean autoRecognize(String ids);
+
+    boolean moveScanFile(List<Long> ids, Long nodeId);
+}

+ 5 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/service/ScanFolderService.java

@@ -0,0 +1,5 @@
+package org.springblade.archive.service;
+
+public interface ScanFolderService {
+
+}

+ 13 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ArchivesAutoServiceImpl.java

@@ -5157,6 +5157,19 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 		autoMapper.removeFilesByArchiveIds(archiveIds);
 	}
 
+	@Override
+	@Async
+	public void reCreateArchiveAuto1(String ids) {
+		for (Long id : Func.toLongList(ids)) {
+			//先查出勾选的案卷
+			ArchivesAuto archivesAuto=this.getById(id);
+			//查出所有案卷文件
+			List<ArchiveFile>archiveFileList=archiveFileClient.getArchiveFileByArchiveIds(id+"");
+			//设置案卷页码和四要素
+			this.reCreateArchiveAuto(archivesAuto, archiveFileList);
+		}
+	}
+
 	/**
 	 * 分组适配方法:按nodeId分组文件并进行页数合并处理
 	 *

+ 520 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ScanFileServiceImpl.java

@@ -0,0 +1,520 @@
+package org.springblade.archive.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import org.springblade.archive.entity.ArchivesAuto;
+import org.springblade.archive.entity.ScanFile;
+import org.springblade.archive.entity.ScanFolder;
+import org.springblade.archive.mapper.ScanFileMapper;
+import org.springblade.archive.mapper.ScanFolderMapper;
+import org.springblade.archive.service.ScanFileService;
+import org.springblade.archive.utils.FileUtils;
+import org.springblade.archive.vo.ScanFolderVO;
+import org.springblade.business.entity.ArchiveFile;
+import org.springblade.business.feign.ArchiveFileClient;
+import org.springblade.common.utils.SnowFlakeUtil;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.oss.model.BladeFile;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.manager.entity.ArchiveTreeContract;
+import org.springblade.manager.feign.ArchiveTreeContractClient;
+import org.springblade.resource.feign.NewIOSSClient;
+import org.springframework.beans.BeanUtils;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Service;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
+import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.stream.Collectors;
+
+
+@Service
+@AllArgsConstructor
+public class ScanFileServiceImpl  extends ServiceImpl<ScanFileMapper, ScanFile> implements ScanFileService {
+//    //文件根目录
+//    private static final String ROOT_PREFIX = "D:" + File.separator + "ScanPDF";
+//    //备份文件根目录
+//    private static final String ROOT_PREFIX_back = "D:" + File.separator + "ScanBackPDF";
+    //文件根目录
+    private static final String ROOT_PREFIX = "/mnt/sdc/ScanPDF";
+    //备份文件根目录
+    private static final String ROOT_PREFIX_back = "/mnt/sdc/ScanBackPDF";
+
+    private final ScanFolderMapper scanFolderMapper;
+    private final ScanFileMapper scanFileMapper;
+    private final NewIOSSClient newIOSSClient;
+    private final ArchiveTreeContractClient archiveTreeContractClient;
+    private final JdbcTemplate jdbcTemplate;
+    private final ArchiveFileClient archiveFileClient;
+
+
+
+
+    @Override
+    public void scanAndSave(Long contractId, Long projectId) {
+        //扫描文件夹
+        scanAndSaveFolder(contractId, projectId);
+        //扫描文件
+        scanAndSaveFiles(contractId,projectId);
+        //整理编号,序号
+        sortNumber(contractId, projectId);
+    }
+
+    @Override
+    public List<ScanFolderVO> getScanFolder(Long contractId, Long projectId) {
+        List<ScanFolder> list = scanFileMapper.getScanFolder(contractId,projectId);
+        // 将ScanFolder转换为ScanFolderVO
+        List<ScanFolderVO> voList = list.stream().map(scanFolder -> {
+            ScanFolderVO vo = new ScanFolderVO();
+            // 复制属性
+            BeanUtils.copyProperties(scanFolder, vo);
+            // 初始化子节点列表
+            vo.setChilds(new ArrayList<>());
+            // 默认为没有子节点,后续会重新判断
+            vo.setHasChildren(false);
+            return vo;
+        }).collect(Collectors.toList());
+
+        // 构建父子关系
+        Map<Long, ScanFolderVO> voMap = voList.stream()
+                .collect(Collectors.toMap(ScanFolderVO::getId, v -> v));
+
+        List<ScanFolderVO> result = new ArrayList<>();
+
+        for (ScanFolderVO vo : voList) {
+            Long parentId = vo.getParentId();
+            if (parentId == null || parentId == 0) {
+                // 没有父节点的作为根节点
+                result.add(vo);
+            } else {
+                // 有父节点的添加到对应父节点的子列表中
+                ScanFolderVO parent = voMap.get(parentId);
+                if (parent != null) {
+                    parent.getChilds().add(vo);
+                    parent.setHasChildren(true); // 标记父节点有子节点
+                }
+            }
+        }
+
+        return result;
+
+    }
+
+    @Override
+    public IPage<ScanFile> getScanFile(Long contractId, Long projectId, Long folderId, Query query,Integer move) {
+        IPage<ScanFile> page = new Page<>(query.getCurrent(), query.getSize());
+        page=baseMapper.getScanFile(page,contractId,projectId,folderId,move);
+        return page;
+    }
+
+    @Override
+    public String deleteScanFile(String ids) {
+        List<Long> longList = Func.toLongList(ids);
+        List<ScanFile> scanFiles = baseMapper.selectList(new LambdaQueryWrapper<>(ScanFile.class).in(ScanFile::getId, longList));
+        List<String> fileNames = scanFiles.stream().filter(o-> !StringUtil.isBlank(o.getFileName())).map(o -> o.getFileName()).collect(Collectors.toList());
+        baseMapper.removeScan(longList);
+        newIOSSClient.removeFiles(fileNames);
+        return "";
+    }
+
+    @Override
+    public Boolean autoRecognize(String ids) {
+        List<Long> longList = Func.toLongList(ids);
+        List<ScanFile> scanFiles = baseMapper.selectList(new LambdaQueryWrapper<>(ScanFile.class).in(ScanFile::getId, longList));
+        return true;
+    }
+
+
+    @Override
+    public boolean moveScanFile(List<Long> ids, Long nodeId) {
+        try {
+            List<ScanFile> scanFiles = baseMapper.selectList(new LambdaQueryWrapper<>(ScanFile.class).in(ScanFile::getId, ids));
+            ArchiveTreeContract contract = archiveTreeContractClient.getArchiveTreeContractById(nodeId);
+            String sql="select IFNULL(max(sort),0) from u_archive_file where project_id="+contract.getProjectId()+" and contract_id="+contract.getContractId()+" and is_deleted=0";
+            Integer sort = jdbcTemplate.queryForObject(sql, Integer.class);
+            List<ArchiveFile>list=new ArrayList<>();
+            if(contract!=null){
+                for (ScanFile file : scanFiles) {
+                    ArchiveFile archiveFile = new ArchiveFile();
+                    archiveFile.setId(SnowFlakeUtil.getId());
+                    archiveFile.setProjectId(contract.getProjectId()+"");
+                    archiveFile.setContractId(contract.getContractId()+"");
+                    archiveFile.setNodeId(nodeId+"");
+                    archiveFile.setFileNumber(file.getFileNum());
+                    archiveFile.setFileName(file.getFileName());
+                    archiveFile.setFileTime(file.getFileDate());
+                    archiveFile.setFileUrl(file.getOssUrl());
+                    archiveFile.setPdfFileUrl(file.getOssUrl());
+                    archiveFile.setFilePage(file.getFileSize()!=null?Integer.parseInt(file.getFileSize()):0);
+                    archiveFile.setIsApproval(0);
+                    archiveFile.setIsNeedCertification(0);
+                    archiveFile.setIsCertification(0);
+                    archiveFile.setDutyUser(file.getResponsible());
+                    archiveFile.setIsArchive(0);
+                    archiveFile.setSourceType(2);
+                    archiveFile.setIsElement(0);
+                    archiveFile.setClassify(contract.getClassify());
+                    archiveFile.setNodeTreeStructure(contract.getTreeStructure());
+                    archiveFile.setSort(sort++);
+                    list.add(archiveFile);
+                }
+                archiveFileClient.saveBatchArchiveFile(list);
+                for (ScanFile file : scanFiles) {
+                    file.setIsMove(1);
+                }
+                this.updateBatchById(scanFiles);
+            }
+            return true;
+        }catch (Exception e){
+            return false;
+        }
+    }
+
+    /**
+     * 入口方法:扫描并入库指定contractId的所有文件夹
+     * @param contractId 传入的合同ID(对应D:\PDF下的文件夹名)
+     * @param projectId 传入的项目ID
+     */
+    public R scanAndSaveFolder(Long contractId, Long projectId) {
+        // 1. 构建合同对应的根文件夹路径(D:\PDF\${contractId})
+        String contractFolderPath = ROOT_PREFIX + File.separator + contractId;
+        System.out.println("contractFolderPath=" + contractFolderPath);
+        File contractFolder = new File(contractFolderPath);
+
+        // 校验根文件夹是否存在且为目录
+        if (!contractFolder.exists() || !contractFolder.isDirectory()) {
+            return R.fail("错误:根文件夹不存在或非法,路径=" + contractFolderPath);
+        }
+
+        // 2. 扫描一级文件夹(parent_id=0)并入库,同时递归子文件夹
+        File[] firstLevelFolders = contractFolder.listFiles(File::isDirectory);
+        if (firstLevelFolders == null || firstLevelFolders.length == 0) {
+            return R.fail("提示:合同ID=" + contractId + "的根文件夹下无任何子文件夹");
+        }
+
+        // 遍历一级文件夹,逐个入库并递归子目录
+        for (File firstLevelFolder : firstLevelFolders) {
+            String folderName = firstLevelFolder.getName();
+            Long folderId = scanFolderMapper.getId(folderName,contractId,projectId);
+            if(folderId==null){
+                folderId=SnowFlakeUtil.getId(); // 生成随机ID
+            }
+            String filePath = firstLevelFolder.getAbsolutePath();
+            // 创建一级目录的ScanFolder对象(parent_id=0)
+            ScanFolder firstLevelScanFolder = new ScanFolder(
+                    folderId,
+                    0L,          // 一级目录父ID固定为0
+                    projectId,
+                    contractId,
+                    folderName,
+                    filePath,
+                    0
+            );
+                 // 入库一级目录
+                 checkInsertScanFolder(firstLevelScanFolder);
+                // 递归扫描当前一级目录下的所有子文件夹(子目录的父ID=当前一级目录ID)
+                recursiveScanSubFolders(firstLevelFolder, folderId, projectId, contractId);
+
+        }
+        return R.success("扫描并入库成功");
+    }
+
+    /**
+     * 递归扫描子文件夹并入库
+     * @param parentFolder 父文件夹对象(当前要扫描子目录的文件夹)
+     * @param parentFolderId 父文件夹在数据库中的ID(子目录的parent_id)
+     * @param projectId 项目ID(透传)
+     * @param contractId 合同ID(透传)
+     */
+    private void recursiveScanSubFolders(File parentFolder, Long parentFolderId, Long projectId, Long contractId) {
+        // 获取父文件夹下的所有子文件夹
+        File[] subFolders = parentFolder.listFiles(File::isDirectory);
+        if (subFolders == null || subFolders.length == 0) {
+            System.out.println("递归终止:父ID=" + parentFolderId + "(名称=" + parentFolder.getName() + ")下无子文件夹");
+            return;
+        }
+
+        // 遍历每个子文件夹,入库并继续递归其下的子目录
+        for (File subFolder : subFolders) {
+            String subFolderName = subFolder.getName();
+            Long subFolderId = scanFolderMapper.getId(subFolderName,contractId,projectId);
+            if(subFolderId==null){
+                subFolderId=SnowFlakeUtil.getId(); // 生成随机ID
+            }
+            String filePath = subFolder.getAbsolutePath();
+
+            // 创建子目录的ScanFolder对象(parent_id=父文件夹ID)
+            ScanFolder subScanFolder = new ScanFolder(
+                    subFolderId,
+                    parentFolderId, // 关联父目录ID
+                    projectId,
+                    contractId,
+                    subFolderName,
+                    filePath,
+                    0
+            );
+                // 入库子目录
+                 checkInsertScanFolder(subScanFolder);
+                // 递归扫描当前子目录下的子文件夹
+                recursiveScanSubFolders(subFolder, subFolderId, projectId, contractId);
+        }
+    }
+
+    /**
+     * 检查文件夹是否存在,不存在则新增
+     * @param scanFolder
+     * @return
+     */
+    public boolean checkInsertScanFolder(ScanFolder scanFolder) {
+        int result = scanFolderMapper.exists(scanFolder.getProjectId(), scanFolder.getContractId(), scanFolder.getFolderName());
+        if(result>0){
+            return false;
+        }else {
+            scanFolderMapper.insert(scanFolder);
+        }
+        return true;
+    }
+
+
+    /**
+     * 扫描文件并入库
+     * @param contractId
+     * @param projectId
+     */
+    public R scanAndSaveFiles(Long contractId, Long projectId) {
+        // 1. 构建合同对应的根文件夹路径(D:\PDF\${contractId})
+        String contractFolderPath = ROOT_PREFIX + File.separator + contractId;
+        System.out.println("contractFolderPath=" + contractFolderPath);
+        File contractFolder = new File(contractFolderPath);
+
+        // 校验根文件夹是否存在且为目录
+        if (!contractFolder.exists() || !contractFolder.isDirectory()) {
+            return R.fail("错误:根文件夹不存在或非法,路径=" + contractFolderPath);
+        }
+
+        System.out.println("开始扫描合同ID=" + contractId + "下的所有文件...");
+        // 递归扫描所有文件
+        recursiveScanFiles(contractFolder, projectId, contractId);
+        System.out.println("合同ID=" + contractId + "的文件扫描入库完成");
+        return R.success("扫描并入库完成");
+    }
+
+    @Override
+    public void sortNumber(Long contractId, Long projectId) {
+        List<ScanFile> scanFiles = scanFileMapper.selectList(new LambdaQueryWrapper<ScanFile>().eq(ScanFile::getContractId, contractId).eq(ScanFile::getProjectId, projectId).isNull(ScanFile::getSort).isNull(ScanFile::getDigitalNum).orderByAsc(ScanFile::getCreatTime));
+        Integer maxDigitalNum=scanFileMapper.selectMaxDigitalNum(contractId,projectId);
+        if(maxDigitalNum==null||maxDigitalNum<1){
+            maxDigitalNum=0;
+        }
+        Map<Long, List<ScanFile>> scanFilesMap = scanFiles.stream()
+                .collect(Collectors.groupingBy(ScanFile::getFolderId));
+        for (Map.Entry<Long, List<ScanFile>> entry : scanFilesMap.entrySet()) {
+            Integer MaxSort=scanFileMapper.selectMaxSort(contractId,projectId,entry.getKey());
+            if(MaxSort==null||MaxSort<1){
+                MaxSort=0;
+            }
+            List<ScanFile> list = entry.getValue();
+            for (ScanFile scanFile : list) {
+                scanFile.setSort(++MaxSort);
+                scanFile.setDigitalNum(++maxDigitalNum);
+                scanFileMapper.updateById(scanFile);
+            }
+        }
+    }
+
+
+    /**
+     * 递归扫描文件夹中的所有文件并入库
+     * @param currentFolder 当前扫描的文件夹
+     * @param projectId 项目ID
+     * @param contractId 合同ID
+     */
+    private void recursiveScanFiles(File currentFolder, Long projectId, Long contractId) {
+        // 1. 先处理当前文件夹中的文件
+        File[] files = currentFolder.listFiles(File::isFile);
+        if (files != null && files.length > 0) {
+            for (File file : files) {
+                processFile(file, currentFolder, projectId, contractId);
+            }
+        }
+
+        // 2. 递归处理子文件夹
+        File[] subFolders = currentFolder.listFiles(File::isDirectory);
+        if (subFolders != null && subFolders.length > 0) {
+            for (File subFolder : subFolders) {
+                recursiveScanFiles(subFolder, projectId, contractId);
+            }
+        }
+    }
+
+    /**
+     * 处理单个文件:获取信息并入库
+     */
+    private void processFile(File file, File parentFolder, Long projectId, Long contractId) {
+        String fileMD5 = calculateFileMD5(file);
+        try {
+            // 获取文件创建时间
+            BasicFileAttributes attr = Files.readAttributes(file.toPath(), BasicFileAttributes.class);
+            Date date = new Date(attr.creationTime().toMillis());
+            SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            String createTime = sdf1.format(date); // 2023-12-25 14:30:45
+            // 获取文件信息
+            String fileName = file.getName();
+            String filePath = file.getAbsolutePath();
+            String folderName = parentFolder.getName();
+            // 查询对应的folder_id
+            Long folderId = scanFolderMapper.getId(folderName,contractId,projectId);
+            if (folderId == null) {
+                System.err.println("警告:未找到对应的文件夹记录,跳过文件:" + filePath +
+                        " (folderName=" + folderName + ")");
+                return;
+            }
+            // 检查文件是否已存在(根据project_id, contract_id, file_path判断)
+            if (fileExists(projectId, contractId, folderName)) {
+                System.out.println("文件已存在,跳过:" + filePath);
+                return;
+            }
+            BladeFile bladeFile = newIOSSClient.uploadFile(fileName, filePath);
+            if(bladeFile==null){
+                return;
+            }
+            String oSSFileMD5 = calculateOSSFileMD5(bladeFile.getLink());
+            if (fileMD5 != null && !fileMD5.equals(oSSFileMD5)) {
+                return;
+            }
+            String pdfNum = FileUtils.getPdfNum(bladeFile.getLink());
+            // 生成随机ID
+            Long fileId = SnowFlakeUtil.getId();
+            // 创建文件实体
+            ScanFile scanFile = new ScanFile(
+                    fileId,
+                    projectId,
+                    contractId,
+                    folderId,
+                    null,//序号
+                    null,//数字编号
+                    null,//文件编号
+                    fileName,
+                    pdfNum,//文件页数
+                    null,//文件日期
+                    createTime,
+                    filePath,
+                    bladeFile.getLink(),//OSS路径
+                    null,//负责人
+                    0,
+                    0
+            );
+            // 入库
+            if (scanFileMapper.insert(scanFile)>0) {
+                System.out.println("文件入库成功:" + filePath);
+                try {
+                    // 构建备份文件路径,保持原有的文件夹结构
+                    if (filePath.startsWith(ROOT_PREFIX)) {
+                        // 截取原始路径中除根目录外的部分
+                        String relativePath = filePath.substring(ROOT_PREFIX.length());
+                        System.out.println("relativePath: " + relativePath);
+                        // 构建备份文件完整路径
+                        String backupFilePath = ROOT_PREFIX_back + relativePath;
+                        System.out.println("backupFilePath: " + backupFilePath);
+                        File backupFile = new File(backupFilePath);
+                        // 创建备份文件所在的目录
+                        File backupDir = backupFile.getParentFile();
+                        if (!backupDir.exists()) {
+                            boolean dirCreated = backupDir.mkdirs();
+                            if (!dirCreated) {
+                                throw new IOException("无法创建备份目录: " + backupDir.getAbsolutePath());
+                            }
+                        }
+                        // 复制文件到备份目录
+                        Files.copy(file.toPath(), backupFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+                        String fileMD5New = calculateFileMD5(backupFile);
+                        if (fileMD5New != null && fileMD5New.equals(fileMD5)) {
+                            // 备份成功后删除源文件
+                            file.delete();
+                        }
+                    }
+                } catch (IOException e) {
+                    System.err.println("文件备份失败:" + filePath + ",错误信息:" + e.getMessage());
+                }
+            }
+        } catch (IOException e) {
+            System.err.println("处理文件时出错:" + file.getAbsolutePath() + ",错误信息:" + e.getMessage());
+        }
+    }
+    private Boolean fileExists(Long projectId, Long contractId, String folderName){
+        Integer i = scanFileMapper.exists(projectId, contractId, folderName);
+        if(i==1){
+            return true;
+        }else {
+            return false;
+        }
+    }
+
+    private String calculateFileMD5(File file) {
+        try {
+            MessageDigest md = MessageDigest.getInstance("MD5");
+            try (InputStream is = Files.newInputStream(file.toPath());
+                 DigestInputStream dis = new DigestInputStream(is, md)) {
+                byte[] buffer = new byte[8192];
+                while (dis.read(buffer) != -1) {
+                    // 读取文件内容以计算MD5
+                }
+            }
+            byte[] digest = md.digest();
+            StringBuilder sb = new StringBuilder();
+            for (byte b : digest) {
+                sb.append(String.format("%02x", b));
+            }
+            return sb.toString();
+        } catch (Exception e) {
+            System.err.println("计算本地文件MD5失败:" + file.getAbsolutePath() + ",错误信息:" + e.getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * 计算OSS文件的MD5值
+     * @param ossUrl OSS文件路径
+     * @return MD5值,如果出错返回null
+     */
+    private String calculateOSSFileMD5(String ossUrl) {
+        try {
+            MessageDigest md = MessageDigest.getInstance("MD5");
+
+            // 使用URL下载OSS文件
+            URL url = new URL(ossUrl);
+            try (InputStream is = url.openStream();
+                 DigestInputStream dis = new DigestInputStream(is, md)) {
+                byte[] buffer = new byte[8192];
+                while (dis.read(buffer) != -1) {
+                    // 读取文件内容以计算MD5
+                }
+            }
+
+            byte[] digest = md.digest();
+            StringBuilder sb = new StringBuilder();
+            for (byte b : digest) {
+                sb.append(String.format("%02x", b));
+            }
+            return sb.toString();
+        } catch (Exception e) {
+            System.err.println("计算OSS文件MD5失败:" + ossUrl + ",错误信息:" + e.getMessage());
+            return null;
+        }
+    }
+}

+ 16 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ScanFolderServiceImpl.java

@@ -0,0 +1,16 @@
+package org.springblade.archive.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.AllArgsConstructor;
+import org.springblade.archive.entity.ArchivesAuto;
+import org.springblade.archive.entity.ScanFolder;
+import org.springblade.archive.mapper.ArchivesAutoMapper;
+import org.springblade.archive.service.ScanFolderService;
+import org.springblade.core.mp.base.BaseServiceImpl;
+import org.springblade.manager.entity.WbsTreeSynchronousRecord;
+import org.springframework.stereotype.Service;
+
+@Service
+@AllArgsConstructor
+public class ScanFolderServiceImpl  implements ScanFolderService {
+}

+ 18 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/utils/FileUtils.java

@@ -9,6 +9,7 @@ import com.sun.image.codec.jpeg.JPEGImageEncoder;
 import lombok.extern.slf4j.Slf4j;
 
 import org.apache.commons.lang.StringUtils;
+import org.apache.pdfbox.pdmodel.PDDocument;
 import org.apache.poi.hssf.usermodel.HSSFPrintSetup;
 import org.apache.poi.ss.usermodel.ClientAnchor;
 import org.apache.poi.ss.usermodel.Sheet;
@@ -879,4 +880,21 @@ public class FileUtils {
         return String.valueOf(pageNumber);
     }
 
+    public static String getPdfNum(String url) {
+        try {
+            if (url.isEmpty() || url.equals("")) {
+                return "0";
+            }
+            InputStream pdfInputStream = CommonUtil.getOSSInputStream(url);
+            //获取这份文件的页数并设置签章策略
+            //获取PDF文件
+            PDDocument document = PDDocument.load(pdfInputStream);
+            int page = document.getPages().getCount();
+            return page + "";
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return "";
+    }
+
 }

+ 3 - 4
blade-service/blade-business/src/main/java/org/springblade/business/controller/InformationWriteQueryController.java

@@ -5393,7 +5393,7 @@ public R<Object> customAddContractNode(@RequestBody CustomAddContractNodeDTO dto
      *
      * @return 结果
      */
-    @GetMapping("/updateCheckPdfInfo")
+    @PostMapping("/updateCheckPdfInfo")
     @ApiOperationSupport(order = 40)
     @ApiOperation(value = "添加pdf检查接口")
     @ApiImplicitParams(value = {
@@ -5401,9 +5401,8 @@ public R<Object> customAddContractNode(@RequestBody CustomAddContractNodeDTO dto
             @ApiImplicitParam(name = "classify", value = "1施工2监理",required = true),
             @ApiImplicitParam(name = "ids", value = "多个用,拼接", required = true)
     })
-    public  R updateCheckPdfInfo(@RequestParam String type, @RequestParam String ids,@RequestParam String classify) {
-
-        return informationQueryService.updateCheckPdfInfo(type, ids,classify);
+    public  R updateCheckPdfInfo(@RequestBody ChekPdfVo chekPdfVo) {
+        return informationQueryService.updateCheckPdfInfo(chekPdfVo.getType(), chekPdfVo.getIds(), chekPdfVo.getClassify());
     }
 
     /**

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

@@ -455,4 +455,9 @@ public class ArchiveFileClientImpl implements ArchiveFileClient {
         return fileMapper.getAllArchiveFileByIds(strList);
     }
 
+    @Override
+    public void saveBatchArchiveFile(List<ArchiveFile> list) {
+        iArchiveFileService.saveBatch(list);
+    }
+
 }

+ 8 - 2
blade-service/blade-business/src/main/java/org/springblade/business/mapper/ArchiveFileMapper.xml

@@ -156,7 +156,7 @@
     </select>
 
     <select id="selectArchiveFilePage" resultMap="archiveFileResultMap">
-        select u.id,u.project_id,u.contract_id,u.node_id,u.file_number,u.file_name,u.file_time,u.file_url,u.pdf_file_url,u.file_page,u.is_approval,u.is_certification,u.is_need_certification,u.duty_user,u.create_user,u.create_dept,u.create_time,u.update_user,u.update_time,u.status,u.is_deleted,u.sheet_type,u.sheet_source,u.drawing_no,u.cite_change_number,u.certification_time,u.e_visa_file,u.node_ext_id,u.file_type,u.archive_id,u.origin_id,u.filming_time,u.filmingor_time,u.tag_id,u.pic_code,u.refer_code,u.film_code,u.width,u.height,u.ftime,u.del_time,u.sort,u.box_name,u.box_number,u.is_auto_file,u.is_archive,u.page_num,u.file_size,u.source_type,u.is_element,u.pdf_page_url,u.fid,u.rectification,u.classify,u.m_wbs_tree_contract_p_key_id,u.u_image_classification_file_id,u.archive_file_storage_type,u.node_tree_structure,u.date_name,u.archive_file_stroage_type,u.out_id,u.sort_num,u.is_lock,u.archive_sort,u.remark,u.m_album_id
+        select u.id,u.project_id,u.contract_id,u.node_id,u.file_number,u.file_name,u.file_time,u.file_url,u.pdf_file_url,u.file_page,u.is_approval,u.is_certification,u.is_need_certification,u.duty_user,u.create_user,u.create_dept,u.create_time,u.update_user,u.update_time,u.status,u.is_deleted,u.sheet_type,u.sheet_source,u.drawing_no,u.cite_change_number,u.certification_time,u.e_visa_file,u.node_ext_id,u.file_type,u.archive_id,u.origin_id,u.tag_id,u.pic_code,u.refer_code,u.film_code,u.width,u.height,u.sort,u.box_name,u.box_number,u.is_auto_file,u.is_archive,u.page_num,u.file_size,u.source_type,u.is_element,u.pdf_page_url,u.fid,u.rectification,u.classify,u.m_wbs_tree_contract_p_key_id,u.u_image_classification_file_id,u.archive_file_storage_type,u.node_tree_structure,u.date_name,u.archive_file_stroage_type,u.out_id,u.sort_num,u.is_lock,u.archive_sort,u.remark,u.m_album_id
         from u_archive_file u
         <if test="vo.nodeIds != null and vo.nodeIds != ''">
             left join m_archive_tree_contract t on t.id = u.node_id and t.is_deleted= 0
@@ -240,10 +240,16 @@
             )
         </if>
         order by
+        u.sort,
         <if test="vo.nodeIds != null and vo.nodeIds != ''">
+            case
+            when t.tree_sort regexp '^[a-zA-Z]' then 0  -- 字母开头的排在前面
+            when t.tree_sort regexp '^[0-9]' then 1    -- 数字开头的排在后面
+            else 2                                   -- 其他情况
+            end,
             t.tree_sort,
         </if>
-        u.sort,u.sort_num,u.create_time
+        u.sort_num,u.create_time
         limit #{current}, #{size}
     </select>
 

+ 15 - 3
blade-service/blade-business/src/main/java/org/springblade/business/service/impl/ArchiveFileServiceImpl.java

@@ -328,13 +328,20 @@ public class ArchiveFileServiceImpl extends BaseServiceImpl<ArchiveFileMapper, A
 
     @Override
     public boolean sortByFileTime(Long nodeId) {
-        String sql = "SELECT id,file_time,node_id FROM u_archive_file WHERE node_id IN (SELECT id FROM m_archive_tree_contract WHERE  FIND_IN_SET(" + nodeId + ",ancestors)) AND is_deleted=0";
+        String sql = "SELECT id,file_time,node_id,sort FROM u_archive_file WHERE node_id IN (SELECT id FROM m_archive_tree_contract WHERE  FIND_IN_SET(" + nodeId + ",ancestors)) AND is_deleted=0";
         List<ArchiveFile> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(ArchiveFile.class));
         if (list.size() > 0) {
             Map<String, List<ArchiveFile>> group = list.stream()
                     .collect(Collectors.groupingBy(ArchiveFile::getNodeId));
             for (Map.Entry<String, List<ArchiveFile>> entry : group.entrySet()) {
                 List<ArchiveFile> archiveFiles = entry.getValue();
+                // 从archiveFiles列表中提取sort字段,处理null值
+                List<Integer> sortValues = archiveFiles.stream()
+                        .map(file -> {
+                            // 如果sort字段为null,返回0;否则返回实际值
+                            return file.getSort() != null ? file.getSort() : 0;
+                        })
+                        .collect(Collectors.toList());
                 archiveFiles.sort((a, b) -> {
                     if (a.getFileTime() == null && b.getFileTime() == null) return 0;
                     if (a.getFileTime() == null) return -1;
@@ -342,8 +349,13 @@ public class ArchiveFileServiceImpl extends BaseServiceImpl<ArchiveFileMapper, A
                     return a.getFileTime().compareTo(b.getFileTime());
                 });
                 int sort = 0;
-                for (ArchiveFile file : archiveFiles) {
-                    file.setSort(sort++);
+                for (int i = 0; i < archiveFiles.size(); i++ ) {
+                    Integer sortValue = sortValues.get(i);
+                    if(sortValue!=0){
+                        archiveFiles.get(i).setSort(sortValue);
+                    }else {
+                        archiveFiles.get(i).setSort(sort++);
+                    }
                 }
                 this.updateBatchById(archiveFiles);
             }

+ 2 - 0
blade-service/blade-business/src/main/java/org/springblade/business/service/impl/InformationQueryServiceImpl.java

@@ -44,6 +44,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.jdbc.core.BeanPropertyRowMapper;
 import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
 import java.io.File;
@@ -1113,6 +1114,7 @@ public class InformationQueryServiceImpl extends BaseServiceImpl<InformationQuer
     }
 
     @Override
+//    @Async
     public R updateCheckPdfInfo(String type, String ids,String classify) {
         if(type==null || Func.isNull(type)){
             return R.fail("type不能weinull");

+ 1 - 0
blade-service/blade-business/src/main/java/org/springblade/business/service/impl/TrialSelfInspectionRecordServiceImpl.java

@@ -1161,6 +1161,7 @@ public class TrialSelfInspectionRecordServiceImpl extends BaseServiceImpl<TrialS
             }
 
             //------保存实体表数据、试验记录信息、生成PDF------
+            dto.setTableIds(tableIds);
             this.submitTrialData(obj, dto);
 
             //------关联原材料检测报告------

+ 6 - 3
blade-service/blade-e-visa/src/main/java/org/springblade/evisa/controller/ChekSignData.java

@@ -14,9 +14,11 @@ import org.springframework.web.bind.annotation.RestController;
 
 import javax.annotation.Resource;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 /**
  * 清表基础数据表 控制器
@@ -55,16 +57,17 @@ public class ChekSignData {
             for (ScrSignInfoVO dataInfo : query) {
                 if (executor.getQueue().size()<=30 ) {
                     Long nodeId = dataInfo.getId();
-                    Boolean aBoolean = RedisTemplate.hasKey("chek-" + nodeId);
+                    // 兼容以前 u_information_query 与 u_task 一对多的关系
+                    Boolean aBoolean = RedisTemplate.hasKey("chek-" + nodeId + dataInfo.getProcessInstanceId());
 
                     if (!aBoolean) {
-                        RedisTemplate.opsForValue().set("chek-" + nodeId, "1",3600, TimeUnit.SECONDS);
+                        RedisTemplate.opsForValue().set("chek-" + nodeId + dataInfo.getProcessInstanceId(), "1",3600, TimeUnit.SECONDS);
                         CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> {
                             try {
                                 /*===============执行批量任务===============*/
                                 scrDataService.sctTaskBatch(dataInfo);
                             } catch (Exception e) {
-                                RedisTemplate.delete("chek-" + nodeId);
+                                RedisTemplate.delete("chek-" + nodeId + dataInfo.getProcessInstanceId());
                                 e.printStackTrace();
                             }
                         }, executor);

+ 4 - 4
blade-service/blade-e-visa/src/main/java/org/springblade/evisa/service/impl/EVDataServiceImpl.java

@@ -590,7 +590,7 @@ public class EVDataServiceImpl implements EVDataService {
     public void addSignatureDataByAXQZ(List<SealStrategyVO> strategyListByAXQ,String formDataId,String markType) {
         System.out.println("www");
         String deleteSql = "delete from m_sign_data where query_id = '" + formDataId + "' and user_id in( ";
-        String insertsql = "INSERT INTO m_sign_data (id, query_id, text_id, user_id, sign_time, type, img_url, sign_type) VALUES (?, ?, ?, ?, SYSDATE(), ?, ?, ?)";
+        String insertsql = "INSERT INTO m_sign_data (id, query_id, text_id, user_id, sign_time, type, img_url, sign_type, pyzbx, pyzby) VALUES (?, ?, ?, ?, SYSDATE(), ?, ?, ?, ?, ?)";
         List<Object[]> batchArgs = new ArrayList<>();
         for (SealStrategyVO sealStrategyVO : strategyListByAXQ) {
             long newPkId = SnowFlakeUtil.getId(); //主键Id
@@ -598,7 +598,7 @@ public class EVDataServiceImpl implements EVDataService {
             if(sealStrategyVO.isCompanySeal()){
                 type = "2";
             }
-            batchArgs.add(new Object[]{newPkId, formDataId, sealStrategyVO.getKeyword(), sealStrategyVO.getUserId(), type, sealStrategyVO.getImageUrl(), markType});
+            batchArgs.add(new Object[]{newPkId, formDataId, sealStrategyVO.getKeyword(), sealStrategyVO.getUserId(), type, sealStrategyVO.getImageUrl(), markType, sealStrategyVO.getOffSetX(), sealStrategyVO.getOffSetY()});
             deleteSql+= sealStrategyVO.getUserId() +",";
         }
         // 添加更多数据...
@@ -611,13 +611,13 @@ public class EVDataServiceImpl implements EVDataService {
     public void addSignatureDataByDFZX(List<Map<String, Object>> strategyListByAXQ,String formDataId,String markType) {
         System.out.println("www");
         String deleteSql = "delete from m_sign_data where query_id = '" + formDataId + "' and user_id in( ";
-        String insertsql = "INSERT INTO m_sign_data (id, query_id, text_id, user_id, sign_time, type, img_url, sign_type) VALUES (?, ?, ?, ?, SYSDATE(), ?, ?, ?)";
+        String insertsql = "INSERT INTO m_sign_data (id, query_id, text_id, user_id, sign_time, type, img_url, sign_type, pyzbx, pyzby) VALUES (?, ?, ?, ?, SYSDATE(), ?, ?, ?, ?, ?)";
         List<Object[]> batchArgs = new ArrayList<>();
         for (Map<String, Object> map : strategyListByAXQ) {
             long newPkId = SnowFlakeUtil.getId(); //主键Id
             String userId = map.get("userId")+"";
             String type = map.get("type")+"";
-            batchArgs.add(new Object[]{newPkId, formDataId, map.get("keyWord"), userId, type, map.get("sealId"), markType});
+            batchArgs.add(new Object[]{newPkId, formDataId, map.get("keyWord"), userId, type, map.get("sealId"), markType, map.get("pyzbx"), map.get("pyzby")});
             deleteSql+=userId +",";
          }
         // 添加更多数据...

+ 167 - 34
blade-service/blade-e-visa/src/main/java/org/springblade/evisa/service/impl/ScrDataServiceImpl.java

@@ -7,11 +7,10 @@ import org.apache.pdfbox.pdmodel.common.PDRectangle;
 import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
 import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
 import org.springblade.business.vo.ScrSignInfoVO;
-import org.springblade.business.vo.TaskSignInfoVO;
 import org.springblade.common.utils.CommonUtil;
-import org.springblade.core.tool.utils.BeanUtil;
 import org.springblade.core.tool.utils.CollectionUtil;
 import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringUtil;
 import org.springblade.evisa.service.ScrDataService;
 import org.springblade.evisa.utils.PdfAddimgUtil;
 import org.springblade.evisa.vo.SignKeyVO;
@@ -73,7 +72,7 @@ public class ScrDataServiceImpl implements ScrDataService {
             String sql2 = "UPDATE u_information_query set chek_status=3 where id='"+taskApp.getId()+"'";
             jdbcTemplate.execute(sql2);
 
-            RedisTemplate.delete("chek-" + taskApp.getId());
+            RedisTemplate.delete("chek-" + taskApp.getId() + taskApp.getProcessInstanceId());
             System.out.println(taskApp.getProcessInstanceId()+"-"+"总共:" );
             return;
         }
@@ -221,7 +220,7 @@ public class ScrDataServiceImpl implements ScrDataService {
             String sql2 = "UPDATE u_information_query set chek_status=3 where id='"+taskApp.getId()+"'";
             jdbcTemplate.execute(sql2);
         }
-        RedisTemplate.delete("chek-" + taskApp.getId());
+        RedisTemplate.delete("chek-" + taskApp.getId() + taskApp.getProcessInstanceId());
       //  System.out.println(taskApp.getProcessInstanceId()+"-"+"总共:" );
       //  System.out.println(taskApp.getId() +"-"+"总共:" + positions.size() + "-剩下-" + differentElements.size());
     }
@@ -230,16 +229,14 @@ public class ScrDataServiceImpl implements ScrDataService {
 
     // 添加电签策略 -- 安心签
     public List<Map<String, Object>> getStrategyListByAXQ(ScrSignInfoVO task, String ids) {
-        String sql = "select task_user,count(1) as total,initiative,status,(select  count(1) from u_task_parallel where process_instance_id = '" + task.getProcessInstanceId() + "' and initiative=2 and status=2) as pCount from u_task_parallel where process_instance_id = '" + task.getProcessInstanceId() + "'";
+        String sql = "select task_user,initiative,status,(select  count(1) from u_task_parallel where process_instance_id = '" + task.getProcessInstanceId() + "' and initiative=2 and status=2) as pCount from u_task_parallel where process_instance_id = '" + task.getProcessInstanceId() + "'";
         List<Map<String, Object>> mapList = jdbcTemplate.queryForList(sql);
         ArrayList<String> strArray = new ArrayList();
-        int total = 0;
         int pCount = 0;
         if (mapList != null && mapList.size() >= 1) {
             for (int i = 0; i < mapList.size(); i++) {
                 Map<String, Object> map = mapList.get(i);
                 if(i==0){
-                    total = Func.toInt(map.get("total"));
                     pCount = Func.toInt(map.get("pCount"));
                 }
                 if((map.get("initiative")+"").equals("2") && (map.get("status")+"").equals("2")){
@@ -270,8 +267,8 @@ public class ScrDataServiceImpl implements ScrDataService {
             for (int i = 0; i < strArray.size(); i++) {
                 List<Map<String, Object>> maps2 = null;
                 if (dqIds.length() > 0) {
-                    String sqlinfo = " SELECT * from ( SELECT a.id as keyWord,a.project_id,a.pyzbx ,a.pyzby,(SELECT acc_code from blade_user where id='" + strArray.get(i) + "' and is_deleted=0  ) as sealId from m_textdict_info a where  a.type =2 and a.id in (" + dqIds + ")  and sig_role_id in (SELECT DISTINCT c.role_id from m_project_assignment_user c  where c.contract_id=" + task.getContractId() + " and user_id=" + strArray.get(i) + " and c.is_deleted=0 ) ) x where x.sealId is not null ";
-                    System.out.println("东方中讯--签字--" + sqlinfo);
+                    String sqlinfo = " SELECT * from ( SELECT a.id as keyWord,a.project_id,a.pyzbx ,a.pyzby,(SELECT signature_file_url from m_sign_pfx_file where is_register=1 and certificate_user_id='" + strArray.get(i) + "' and is_deleted=0  ) as signature_file_url, '1' as type from m_textdict_info a where  a.type =2 and a.id in (" + dqIds + ")  and sig_role_id in (SELECT DISTINCT c.role_id from m_project_assignment_user c  where c.contract_id=" + task.getContractId() + " and user_id=" + strArray.get(i) + " and c.is_deleted=0 ) ) x where x.signature_file_url is not null ";
+                    System.out.println("安心签--签字--" + sqlinfo);
                     maps2 = jdbcTemplate.queryForList(sqlinfo);
                 }
                 if(CollectionUtil.isNotEmpty(maps2)) {
@@ -295,13 +292,19 @@ public class ScrDataServiceImpl implements ScrDataService {
                             maps.add(keyList.get(0));
                         }
                     }
+                } else {
+                    // 查询 sign_data 表
+                    List<Map<String, Object>> list = jdbcTemplate.queryForList("SELECT * from m_sign_data where query_id = ? and text_id in (?) and user_id = ? and type = 1", task.getId(), dqIds, strArray.get(i));
+                    if (!list.isEmpty()) {
+                        System.out.println("1111111111111111111");
+                    }
                 }
             }
 
 
 
-            String sqlinfo = "SELECT a.id as keyWord,a.pyzbx,a.pyzby,b.certificate_number as sealId from m_textdict_info a ,m_sign_pfx_file b where a.sig_role_id = b.pfx_type and b.project_contract_role like '%" + task.getContractId() + "%' and a.is_deleted=0 and b.is_deleted=0 and a.type=6 and a.id in(" + dqIds + ")";
-            System.out.println("东方中讯--签章--" + sqlinfo);
+            String sqlinfo = "SELECT a.id as keyWord,a.pyzbx,a.pyzby,b.certificate_number as sealId, '2' as type from m_textdict_info a ,m_sign_pfx_file b where a.sig_role_id = b.pfx_type and b.project_contract_role like '%" + task.getContractId() + "%' and a.is_deleted=0 and b.is_deleted=0 and a.type=6 and a.id in(" + dqIds + ")";
+            System.out.println("安心签--签章--" + sqlinfo);
             List<Map<String, Object>> maps2 = jdbcTemplate.queryForList(sqlinfo);
             if(CollectionUtil.isNotEmpty(maps2)) {
                 Map<String, List<Map<String, Object>>> peopleByAge = maps2.stream()
@@ -324,6 +327,8 @@ public class ScrDataServiceImpl implements ScrDataService {
                         maps.add(keyList.get(0));
                     }
                 }
+            } else {
+                System.out.println("22222222222222222222");
             }
         }
         return maps;
@@ -409,6 +414,8 @@ public class ScrDataServiceImpl implements ScrDataService {
             List<String> positions = pdfSignIds.getEVisaConfigList();
             Map<String, String> dataMap = pdfSignIds.getDataMap();
 
+            Map<String, String> dataMap1 = checkSignByPdf(taskApp, pdfSignIds, pdfData);
+
             List<String> sucessUser = new ArrayList<>();
             List<String> sucessCompan = new ArrayList<>();
             String ids = String.join(",", positions);
@@ -423,10 +430,10 @@ public class ScrDataServiceImpl implements ScrDataService {
 
             if (strategyListByDFZX == null || strategyListByDFZX.size() == 0) {
 
-                String sql2 = "UPDATE u_information_query set chek_status=10 where id='" + taskApp.getId() + "'";
+                String sql2 = "UPDATE u_information_query set chek_status=3 where chek_status != 2 and id='" + taskApp.getId() + "'";
                 jdbcTemplate.execute(sql2);
 
-                RedisTemplate.delete("chek-" + taskApp.getId());
+                RedisTemplate.delete("chek-" + taskApp.getId() + taskApp.getProcessInstanceId());
                 System.out.println(taskApp.getProcessInstanceId() + "-" + "总共:");
                 return;
             }
@@ -569,8 +576,8 @@ public class ScrDataServiceImpl implements ScrDataService {
                                 if (pkeyid.length() < 19) {
                                     keyw = keyw - (float) (10 - pkeyid.length() / 2.0);
                                 }
-//                                System.out.println("page = " + (i + 1) + ", type = " + type + ", keyid = " + pkeyid + ", imgX : " + imgX + ", keyw : " + keyw + ", imgY : "
-//                                        + imgY + ", keyh : " + keyh + ", keyword =" + keyData.replace("\r", "\\r") + ", remarkType = " + taskApp.getRemarkType());
+                                System.out.println("page = " + (i + 1) + ", type = " + type + ", keyid = " + pkeyid + ", imgX : " + imgX + ", keyw : " + keyw + ", imgY : "
+                                        + imgY + ", keyh : " + keyh + ", keyword =" + keyData.replace("\r", "\\r") + ", remarkType = " + taskApp.getRemarkType());
                                 if (Math.abs(imgX - keyw) <= threshold && Math.abs(imgY - keyh) <= threshold) {
                                     if (type.equals("1")) { //个人
                                         sucessUser.add(pkeyid);
@@ -603,22 +610,6 @@ public class ScrDataServiceImpl implements ScrDataService {
                 }
             }
 
-//            boolean isSign1 = true;
-//            //判断个人是否签完
-//            List<String> userList = strategyListByDFZX.stream().filter(item -> item.get("type").equals("1")).map(map -> map.get("keyWord").toString()).collect(Collectors.toList());
-//            if (!userList.isEmpty()) {
-//                Set<String> differentElements = new HashSet<>(userList);
-//                sucessUser.forEach(differentElements::remove);
-//                if (!differentElements.isEmpty()) {
-//                    isSign1 = false;
-//                    StringBuilder sb = new StringBuilder();
-//                    sb.append("id = ").append(taskApp.getId()).append(", 人总共:").append(userList.size()).append(", 剩下:").append(differentElements.size());
-//                    for (String element : differentElements) {
-//                        sb.append(", ").append(element);
-//                    }
-//                    System.err.println(sb);
-//                }
-//            }
             if(!sucessUser.isEmpty() && isSign){
                 for(String user:sucessUser){
                     for(String mapkey:dataMap.keySet()){
@@ -637,7 +628,42 @@ public class ScrDataServiceImpl implements ScrDataService {
                     }
                 }
                 if(!dataUserMap.isEmpty()){
-                    isSign = false ;
+                    Map<String, float[]> allKeyPositions = getKeyPositions(pdfSignIds, pdfData);
+                    Set<String> removeKeys = new HashSet<>();
+                    dataUserMap.forEach((id, value) -> {
+                        float[] floats = allKeyPositions.get(value);
+                        if (floats == null || floats.length < 6) {
+                            return;
+                        }
+                        dataMap.forEach((key, value1) -> {
+                            float[] floats1 = allKeyPositions.get(value);
+                            if (floats1.length < 2) {
+                                return;
+                            }
+                            if (floats[0] != floats1[0]) {
+                                return;
+                            }
+                            PDPage page = document.getPage((int) floats[0] - 1);
+                            float pageHeight = page.getMediaBox().getHeight();
+                            float pageWidth = page.getMediaBox().getWidth();
+                            if (Math.abs(floats[5] * pageHeight - floats1[3] * pageHeight) < 1 && Math.abs(floats[4] * pageWidth - floats1[1] * pageWidth) < 1 && !dataUserMap.containsKey(key)) {
+                                removeKeys.add(id);
+                            }
+                        });
+                    });
+                    if (!removeKeys.isEmpty()) {
+                        dataUserMap.entrySet().removeIf(entry -> removeKeys.contains(entry.getKey()));
+                    }
+                    if (!dataUserMap.isEmpty()) {
+                        if (dataMap1 == null) {
+                            dataUserMap.entrySet().removeIf(item -> true);
+                        } else {
+                            dataUserMap.entrySet().removeIf(entry -> !dataMap1.containsKey(entry.getKey()));
+                        }
+                    }
+                    if (!dataUserMap.isEmpty()) {
+                        isSign = false ;
+                    }
                 }
                 System.out.println(taskApp.getId() +"-"+"个人总共:" + sucessUser.size() + "-剩下-" +dataUserMap.keySet().size());
             }
@@ -645,10 +671,117 @@ public class ScrDataServiceImpl implements ScrDataService {
                 String sql2 = "UPDATE u_information_query set chek_status=2 where id='" + taskApp.getId() + "'";
                 jdbcTemplate.execute(sql2);
             } else {
-                String sql2 = "UPDATE u_information_query set chek_status=3 where id='" + taskApp.getId() + "'";
+                String sql2 = "UPDATE u_information_query set chek_status=3 where chek_status != 2 and id='" + taskApp.getId() + "'";
                 jdbcTemplate.execute(sql2);
             }
-            RedisTemplate.delete("chek-" + taskApp.getId());
+            RedisTemplate.delete("chek-" + taskApp.getId() + taskApp.getProcessInstanceId());
+        }
+    }
+
+    /**
+     * 获取所有电签关键字的位置
+     */
+    public Map<String, float[]> getKeyPositions(SignKeyVO pdfSignIds,byte[] pdfData) throws Exception {
+        if (pdfSignIds == null) {
+            return  null;
+        }
+        Map<String, String> dataMap = pdfSignIds.getDataMap();
+        Collection<String> keys = dataMap.values();
+        if (keys.isEmpty()) {
+            return null;
+        }
+        List<PDFIndexInfo> pdfIndexInfo = PdfAddimgUtil.findKeywordPostions(pdfData,  String.join(",", keys));
+        if (pdfIndexInfo.isEmpty()) {
+            return  null;
+        }
+        Map<String, float[]> map = new HashMap<>();
+        for (PDFIndexInfo info : pdfIndexInfo) {
+            map.put(info.getPkeyid(), info.getDataInfo());
+        }
+        return map;
+    }
+
+    public Map<String, String> checkSignByPdf(ScrSignInfoVO taskApp,SignKeyVO pdfSignIds,byte[] pdfData) throws Exception {
+        int threshold = 5;
+        //转换
+        try (PDDocument document = PDDocument.load(pdfData);) {
+            List<String> positions = pdfSignIds.getEVisaConfigList();
+            Map<String, String> dataMap = pdfSignIds.getDataMap();
+            String keyWord = String.join(",", positions);
+            List<String> sucessList = new ArrayList<>();
+            List<PDFIndexInfo> pdfIndexInfo = PdfAddimgUtil.findKeywordPostions(pdfData, keyWord);
+            Map<String, List<PDFIndexInfo>> groupBy = pdfIndexInfo.stream().collect(Collectors.groupingBy(da -> Func.toStr(da.getDataInfo()[0])));
+            for (int i = 0; i < document.getPages().getCount(); i++) {
+                PDPage page = document.getPage(i);
+                List<PDAnnotation> annotations = page.getAnnotations();
+                for (PDAnnotation annotation : annotations) {
+                    if (annotation instanceof PDAnnotationWidget) {
+                        PDRectangle rect = annotation.getRectangle();
+                        float imgW = rect.getWidth();
+                        float imgH = rect.getHeight();
+                        float imgX = rect.getLowerLeftX() + imgW / 2;
+                        float imgY = rect.getLowerLeftY() + imgH / 2;
+                        List<PDFIndexInfo> pdfIndexInfos = groupBy.get((i + 1) + ".0");
+                        if (pdfIndexInfos != null && !pdfIndexInfos.isEmpty()) {
+                            for (PDFIndexInfo pdfInfo : pdfIndexInfos) {
+                                String pkeyid = pdfInfo.getPkeyid();
+                                float[] dataInfo = pdfInfo.getDataInfo();
+                                float keyX = dataInfo[1];
+                                float keyY = dataInfo[2];
+                                float pageHeight = page.getMediaBox().getHeight();
+                                float pageWidth = page.getMediaBox().getWidth();
+
+                                float keyw = keyX * pageWidth;
+                                float keyh = pageHeight - keyY * pageHeight;
+                                float pyzbx = 0.0f;
+                                float pyzby = 0.0f;
+                                if (taskApp.getRemarkType().equals("3")) { //东方中讯
+                                    if (imgH >= 100) {
+                                        // 签章
+                                        keyw = keyw + pyzbx - 21;
+                                        keyh = keyh + pyzby - 16;
+                                    } else {
+                                        keyw = keyw + pyzbx + 10;
+                                        keyh = keyh + pyzby;
+                                    }
+                                } else if (taskApp.getRemarkType().equals("2")) { //东方中讯
+                                    if (imgH >= 100) {
+                                        // 签章
+                                        keyw = keyw + pyzbx - 21;
+                                        keyh = keyh + pyzby - 15;
+                                    } else {
+                                        keyw = keyw + pyzbx + 10;
+                                        keyh = keyh + pyzby;
+                                    }
+                                } else {
+                                    keyw = keyw + pyzbx + 5;
+                                    keyh = keyh + pyzby;
+                                }
+                                if (pkeyid.length() < 19) {
+                                    keyw = keyw - (float) (10 - pkeyid.length() / 2.0);
+                                }
+                                if (Math.abs(imgX - keyw) <= threshold && Math.abs(imgY - keyh) <= threshold) {
+                                    sucessList.add(pkeyid);
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            Map<String, String> dataUserMap = new HashMap<>(dataMap);
+            //判断章是否签完
+            if (!sucessList.isEmpty()) {
+                for (String user : sucessList) {
+                    for (String mapkey : dataMap.keySet()) {
+                        String mapval = dataMap.get(mapkey);
+                        if (mapval.contains(user)) {
+                            dataUserMap.remove(mapkey);
+                        }
+                    }
+                }
+            }
+            return dataUserMap;
         }
     }
 }

+ 10 - 2
blade-service/blade-e-visa/src/main/java/org/springblade/evisa/utils/PdfAddimgUtil.java

@@ -265,6 +265,16 @@ public class PdfAddimgUtil {
                 }
 
                 float[] postions = charPositions.get(positionIndex);
+                try {
+                    // 获取text的结束位置
+                    int endPos = positionIndex + text.length();
+                    if (endPos < content.length()) {
+                        float[] floats = charPositions.get(endPos);
+                        postions = new float[]{postions[0], postions[1], postions[2], floats[0], floats[1], floats[2]};
+                    }
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
                 data.setDataInfo(postions);
                 data.setPkeyid(text);
                 result.add(data);
@@ -406,8 +416,6 @@ public class PdfAddimgUtil {
             String text = stripper.getText(document);
             String[] lines = text.split("[ \\n]+");
 
-            Pattern pattern = Pattern.compile("(\\d{4}[年-]\\d{2}[月-]\\d{2}日?)");
-
             for(int k=0;k<lines.length;k++){
                 String textStr = lines[k];
                 int index = textStr.indexOf("*");