Browse Source

bug

Signed-off-by: liuyc <56808083@qq.com>
liuyc 1 year ago
parent
commit
55afc78bd8

+ 12 - 0
blade-service/blade-user/pom.xml

@@ -43,6 +43,18 @@
             <artifactId>blade-manager-api</artifactId>
             <version>${bladex.project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>com.itextpdf</groupId>
+            <artifactId>itextpdf</artifactId>
+            <version>5.5.13</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springblade</groupId>
+            <artifactId>blade-resource-api</artifactId>
+            <version>2.9.1.RELEASE</version>
+            <scope>compile</scope>
+        </dependency>
     </dependencies>
 
     <build>

+ 23 - 5
blade-service/blade-user/src/main/java/org/springblade/system/user/controller/WbsTreeController.java

@@ -2,6 +2,7 @@ package org.springblade.system.user.controller;
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
+import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiImplicitParam;
@@ -9,6 +10,7 @@ import io.swagger.annotations.ApiImplicitParams;
 import io.swagger.annotations.ApiOperation;
 import lombok.AllArgsConstructor;
 import org.apache.commons.lang.StringUtils;
+import org.springblade.business.vo.InformationQueryVO;
 import org.springblade.core.boot.ctrl.BladeController;
 import org.springblade.core.tool.api.R;
 import org.springblade.core.tool.utils.ObjectUtil;
@@ -16,12 +18,9 @@ import org.springblade.manager.vo.WbsTreeContractLazyVO;
 import org.springblade.system.user.service.IUserService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.StringRedisTemplate;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
-import java.util.List;
+import java.util.*;
 
 @RestController
 @RequestMapping(value = "/wbs")
@@ -88,4 +87,23 @@ public class WbsTreeController extends BladeController {
         return R.data(vos);
     }
 
+    /**
+     * 资料查询
+     *
+     * @author liuyc
+     * @date 2024年1月6日10:28:01
+     */
+    @PostMapping("/informationWriteQuery/page")
+    @ApiOperationSupport(order = 4)
+    @ApiOperation(value = "分页")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "size", value = "当前页条数", required = true),
+            @ApiImplicitParam(name = "current", value = "当前页", required = true),
+            @ApiImplicitParam(name = "wbsId", value = "节点id", required = true),
+            @ApiImplicitParam(name = "contractId", value = "合同段id", required = true)
+    })
+    public R<IPage<InformationQueryVO>> informationWriteQueryPage(@RequestBody InformationQueryVO vo) {
+        return R.data(iUserService.informationWriteQueryPage(vo));
+    }
+
 }

+ 4 - 0
blade-service/blade-user/src/main/java/org/springblade/system/user/mapper/UserMapper.java

@@ -20,6 +20,8 @@ import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import org.apache.ibatis.annotations.Param;
+import org.springblade.business.entity.InformationQuery;
+import org.springblade.business.vo.InformationQueryVO;
 import org.springblade.system.user.entity.User;
 import org.springblade.system.user.excel.UserExcel;
 import org.springblade.system.user.vo.UserContractInfoVO;
@@ -70,4 +72,6 @@ public interface UserMapper extends BaseMapper<User> {
 
     List<UserVO> selectUserListByCondition();
 
+    List<InformationQuery> selectInformationQueryPageTwo(@Param("query") InformationQueryVO vo);
+
 }

+ 79 - 0
blade-service/blade-user/src/main/java/org/springblade/system/user/mapper/UserMapper.xml

@@ -128,4 +128,83 @@
         WHERE b.account NOT IN ("admin", "administrator")
     </select>
 
+    <select id="selectInformationQueryPageTwo" resultType="org.springblade.business.entity.InformationQuery">
+        select
+        query.pdf_trial_url,
+        query.pdf_trial_url_position,
+        query.wbs_id,
+        query.id,
+        query.name,
+        query.number,
+        query.create_time,
+        query.status,
+        query.report_number,
+        query.file_user_id_and_name,
+        query.pdf_url,
+        query.e_visa_pdf_url,
+        query.task_id,
+        query.sj_record_ids
+        from
+        (
+        select
+        iq.pdf_trial_url_position,
+        iq.pdf_trial_url,
+        iq.sort,
+        iq.id,
+        iq.name,
+        iq.number,
+        iq.create_time,
+        iq.status,
+        (case iq.STATUS when 0 THEN null else t.batch end) as report_number,
+        iq.file_user_id_and_name,
+        date_format(iq.create_time,'%Y-%m-%d') as createTimes,
+        iq.pdf_url,
+        iq.e_visa_pdf_url,
+        iq.wbs_id,
+        t.id as task_id,
+        iq.sj_record_ids
+        from u_information_query iq left join (select * from u_task k where k.status!=3 and k.project_id =
+        #{query.projectId} group by
+        form_data_id) t on iq.id = t.form_data_id and t.is_deleted = 0
+        where
+        iq.is_deleted = 0
+        and iq.classify = #{query.classify}
+        and iq.contract_id = #{query.contractId}
+        <if test="query.taskStatus != null and query.taskStatus != ''">
+            <choose>
+                <when test="query.taskStatus == 0">
+                    and (iq.status = 0 or iq.status = 3)
+                </when>
+                <otherwise>
+                    and iq.status = #{query.taskStatus}
+                </otherwise>
+            </choose>
+        </if>
+        <if test="query.sourceType != null and query.sourceType != ''">and iq.source_type = #{query.sourceType}</if>
+        <if test="query.reportNumber != null and query.reportNumber != ''">and t.batch = #{query.reportNumber}</if>
+        <if test="query.fileUserIdAndName != null and query.fileUserIdAndName != ''">and iq.file_user_id_and_name like
+            concat('%',#{query.fileUserIdAndName},'%')
+        </if>
+        <if test="query.queryValue != null and query.queryValue != ''">and (iq.name like
+            concat('%',#{query.queryValue},'%') OR iq.number like concat('%',#{query.queryValue},'%'))
+        </if>
+        <if test="query.firstTitle != null and query.firstTitle != ''">and iq.type = 3</if>
+        <if test="query.firstTitle == null or query.firstTitle == ''">and iq.type != 3</if>
+        <if test="query.wbsIds != null">
+            and iq.wbs_id in
+            <foreach collection="query.wbsIds" item="wbsIdc" open="(" separator="," close=")">
+                #{wbsIdc}
+            </foreach>
+        </if>
+        ) AS query
+        where
+        1 = 1
+        <if test="query.reportNumber != null and query.reportNumber != ''">and report_number = #{query.reportNumber}
+        </if>
+        <if test="query.startTime != null and query.startTime != '' and query.endTime != null and query.endTime != ''">
+            and query.createTimes between #{query.startTime} and #{query.endTime}
+        </if>
+        order by create_time desc
+    </select>
+
 </mapper>

+ 3 - 18
blade-service/blade-user/src/main/java/org/springblade/system/user/service/IUserService.java

@@ -1,25 +1,9 @@
-/*
- *      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.system.user.service;
 
 
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.springblade.business.vo.InformationQueryVO;
 import org.springblade.core.mp.base.BaseService;
 import org.springblade.core.mp.support.Query;
 import org.springblade.manager.dto.SaveUserInfoByProjectDTO;
@@ -30,7 +14,6 @@ import org.springblade.system.user.entity.UserInfo;
 import org.springblade.system.user.entity.UserOauth;
 import org.springblade.system.user.enums.UserEnum;
 import org.springblade.system.user.excel.UserExcel;
-import org.springblade.system.user.vo.UserContractInfoVO;
 import org.springblade.system.user.vo.UserVO;
 
 import java.util.List;
@@ -246,4 +229,6 @@ public interface IUserService extends BaseService<User> {
 
     void deleteContractLocalCache(String contractId);
 
+    IPage<InformationQueryVO> informationWriteQueryPage(InformationQueryVO vo);
+
 }

+ 430 - 1
blade-service/blade-user/src/main/java/org/springblade/system/user/service/impl/UserServiceImpl.java

@@ -1,22 +1,34 @@
 package org.springblade.system.user.service.impl;
 
-import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
 import com.alibaba.nacos.common.utils.StringUtils;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.google.common.collect.Lists;
 import lombok.AllArgsConstructor;
+import org.apache.http.client.utils.DateUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springblade.business.entity.InformationQuery;
+import org.springblade.business.entity.Task;
+import org.springblade.business.entity.TaskParallel;
+import org.springblade.business.entity.TreeContractFirst;
+import org.springblade.business.vo.InformationQueryVO;
+import org.springblade.business.vo.QueryProcessDataVO;
 import org.springblade.common.constant.CommonConstant;
 import org.springblade.common.constant.TenantConstant;
+import org.springblade.common.utils.SnowFlakeUtil;
 import org.springblade.core.log.exception.ServiceException;
 import org.springblade.core.mp.base.BaseServiceImpl;
 import org.springblade.core.mp.support.Condition;
 import org.springblade.core.mp.support.Query;
+import org.springblade.core.oss.model.BladeFile;
 import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.secure.utils.SecureUtil;
 import org.springblade.core.tenant.BladeTenantProperties;
 import org.springblade.core.tool.api.R;
 import org.springblade.core.tool.constant.BladeConstant;
@@ -25,10 +37,14 @@ import org.springblade.core.tool.support.Kv;
 import org.springblade.core.tool.utils.*;
 import org.springblade.manager.dto.SaveUserInfoByProjectDTO;
 import org.springblade.manager.entity.ContractInfo;
+import org.springblade.manager.entity.WbsTreeContract;
 import org.springblade.manager.feign.ContractClient;
 import org.springblade.manager.vo.WbsTreeContractLazyQueryInfoVO;
 import org.springblade.manager.vo.WbsTreeContractLazyVO;
+import org.springblade.manager.vo.WbsTreeContractVO8;
+import org.springblade.resource.feign.NewIOSSClient;
 import org.springblade.system.cache.DictCache;
+import org.springblade.system.cache.ParamCache;
 import org.springblade.system.cache.SysCache;
 import org.springblade.system.entity.Dept;
 import org.springblade.system.entity.Tenant;
@@ -44,6 +60,7 @@ import org.springblade.system.user.mapper.UserMapper;
 import org.springblade.system.user.service.IUserDeptService;
 import org.springblade.system.user.service.IUserOauthService;
 import org.springblade.system.user.service.IUserService;
+import org.springblade.system.user.util.FileUtils;
 import org.springblade.system.user.vo.UserVO;
 import org.springblade.system.user.wrapper.UserWrapper;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -54,6 +71,8 @@ import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.io.File;
+import java.io.FileNotFoundException;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.locks.ReentrantLock;
@@ -77,6 +96,7 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
     /*存储当前合同段contractId_tableOwner对应的节点数量统计缓存信息*/
     private final Map<String, List<WbsTreeContractLazyVO>> localCacheParentCountNodes = new ConcurrentHashMap<>();
 
+    private final NewIOSSClient newIOSSClient;
     private static final String GUEST_NAME = "guest";
     private final IUserDeptService userDeptService;
     private final IUserOauthService userOauthService;
@@ -1212,4 +1232,413 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
         return result;
     }
 
+    @Override
+    public IPage<InformationQueryVO> informationWriteQueryPage(InformationQueryVO vo) {
+        if (ObjectUtil.isEmpty(vo.getContractId())) {
+            throw new ServiceException("未获取到合同段id");
+        }
+        ContractInfo contract = jdbcTemplate.query("SELECT * FROM m_contract_info WHERE id = " + vo.getContractId(), new BeanPropertyRowMapper<>(ContractInfo.class)).stream().findAny().orElse(null);
+        if (ObjectUtil.isEmpty(contract)) {
+            throw new ServiceException("获取合同段信息失败");
+        }
+        vo.setClassify(org.apache.commons.lang.StringUtils.isNotEmpty(vo.getClassifyType()) ? new Integer(vo.getClassifyType()) : contract.getContractType() == null ? 1 : new Integer("1").equals(contract.getContractType()) ? 1 : 2);
+
+        Query query = new Query();
+        query.setSize(vo.getSize());
+        query.setCurrent(vo.getCurrent());
+
+        List<String> submitNodeKeyIds = new ArrayList<>();
+        submitNodeKeyIds.add(vo.getWbsId().toString());
+        WbsTreeContract node = jdbcTemplate.query("SELECT * FROM m_wbs_tree_contract WHERE p_key_id = " + vo.getWbsId(), new BeanPropertyRowMapper<>(WbsTreeContract.class)).stream().findAny().orElse(null);
+        if (node == null) {
+            if (org.apache.commons.lang.StringUtils.isEmpty(vo.getContractIdRelation())) {
+                return null;
+            }
+            //监理
+            node = jdbcTemplate.query("SELECT * FROM m_wbs_tree_contract WHERE id = " + vo.getWbsId() + " AND contract_id = " + vo.getContractIdRelation(), new BeanPropertyRowMapper<>(WbsTreeContract.class)).stream().findAny().orElse(null);
+        }
+
+        if (node != null) {
+            List<WbsTreeContractVO8> lowestNodes = new ArrayList<>();
+            List<QueryProcessDataVO> queryDataResult = new ArrayList<>();
+            if (!new Integer("6").equals(node.getNodeType()) && !Arrays.asList("1,2,3,4".split(",")).contains(node.getMajorDataType() + "")) {
+                //TODO(暂不响应第一层根节点、第二层数据,数据量过多会导致响应超时)
+                if (node.getParentId() == 0) {
+                    throw new ServiceException("请从第三层级节点开始进行检索");
+                } else {
+                    WbsTreeContract wbsTreeContract = jdbcTemplate.query("select parent_id from m_wbs_tree_contract where is_deleted = 0 and status = 1 and type = 1 and id = " + node.getParentId() + " and contract_id = " + node.getContractId(), new BeanPropertyRowMapper<>(WbsTreeContract.class)).stream().findAny().orElse(null);
+                    if (wbsTreeContract != null && wbsTreeContract.getParentId().equals(0L)) {
+                        throw new ServiceException("请从第三层级节点开始进行检索");
+                    }
+
+                    WbsTreeContractVO8 vo8 = BeanUtil.copyProperties(node, WbsTreeContractVO8.class);
+                    this.lowestNodesRecursively(lowestNodes, Collections.singleton(vo8), node.getContractId());
+
+                    List<String> pKeyIds = lowestNodes.stream().map(WbsTreeContractVO8::getPKeyId).map(String::valueOf).collect(Collectors.toList());
+                    List<List<String>> partition = Lists.partition(pKeyIds, 1000);
+                    for (List<String> items : partition) {
+                        String sql = "SELECT wtc.id AS treeId,wtc.p_key_id AS primaryKeyId,wtc.ancestors AS ancestors,wtc.major_data_type AS majorDataType," +
+                                " wtc.node_type AS nodeType,IFNULL(if(length(trim(wtc.full_name)) > 0, wtc.full_name, wtc.node_name), wtc.node_name) AS title," +
+                                " wtc.parent_id AS parentId,uiq.id AS informationQueryId,IFNULL(uiq.status, 0) AS status,uiq.type AS queryType," +
+                                " CASE WHEN uiq.pdf_trial_url IS NULL THEN FALSE ELSE TRUE end AS isExperiment FROM m_wbs_tree_contract AS wtc" +
+                                " LEFT JOIN u_information_query AS uiq ON wtc.p_key_id = uiq.wbs_id AND uiq.classify = " + contract.getContractType() +
+                                " AND uiq.is_deleted = 0 AND wtc.is_deleted = 0 AND wtc.status = 1 WHERE wtc.p_key_id IN (" + org.apache.commons.lang.StringUtils.join(items, ",") + ")";
+                        List<QueryProcessDataVO> result = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(QueryProcessDataVO.class));
+                        if (result.size() > 0) {
+                            queryDataResult.addAll(result);
+                        }
+                    }
+                }
+
+                //首件
+                if (queryDataResult.size() > 0) {
+                    if (org.apache.commons.lang.StringUtils.isNotEmpty(vo.getIsFirst())) {
+                        if (org.apache.commons.lang.StringUtils.isNotEmpty(vo.getFirstTitle())) {
+                            queryDataResult = queryDataResult.stream().filter(qdr -> "3".equals(qdr.getQueryType())).collect(Collectors.toList());
+                        } else {
+                            List<String> treeIds = queryDataResult.stream().map(QueryProcessDataVO::getPrimaryKeyId).distinct().collect(Collectors.toList());
+                            List<TreeContractFirst> firstList = jdbcTemplate.query("SELECT * FROM u_tree_contract_first WHERE is_deleted = 0 AND wbs_node_id IN (" + org.apache.commons.lang.StringUtils.join(treeIds, ",") + ")", new BeanPropertyRowMapper<>(TreeContractFirst.class));
+                            List<String> list = firstList.stream().map(fl -> (fl.getWbsNodeId() + "")).collect(Collectors.toList());
+                            queryDataResult = queryDataResult.stream().filter(qdr -> list.contains(qdr.getPrimaryKeyId())).collect(Collectors.toList());
+                        }
+                    }
+                    submitNodeKeyIds.addAll(queryDataResult.stream().map(QueryProcessDataVO::getPrimaryKeyId).distinct().collect(Collectors.toList()));
+                }
+            }
+
+            vo.setWbsIds(submitNodeKeyIds);
+
+            if (org.apache.commons.lang.StringUtils.isNotEmpty(vo.getContractIdRelation())) {
+                vo.setContractId(Long.valueOf(vo.getContractIdRelation()));
+            }
+        }
+
+        return this.selectInformationQueryPage(Condition.getPage(query), vo, node);
+    }
+
+    private IPage<InformationQueryVO> selectInformationQueryPage(IPage<InformationQueryVO> page, InformationQueryVO vo, WbsTreeContract node) {
+        if (StringUtils.isNotEmpty(vo.getBetweenTime())) {
+            String[] betweenTime;
+            if (vo.getBetweenTime().contains("~")) {
+                String[] between = vo.getBetweenTime().split("~");
+                betweenTime = new String[]{between[0], between[1]};
+            } else {
+                betweenTime = new String[]{vo.getBetweenTime(), DateUtils.formatDate(new Date(), "yyyy-MM-dd")};
+            }
+            vo.setStartTime(betweenTime[0]);
+            vo.setEndTime(betweenTime[1]);
+        }
+
+        if (org.apache.commons.lang.StringUtils.isNotEmpty(vo.getFileUserIdAndName())) {
+            vo.setFileUserIdAndName(vo.getFileUserIdAndName().split("-")[0]);
+        }
+
+        if (vo.getStatus() != null) {
+            vo.setTaskStatus(vo.getStatus().toString());
+        }
+
+        //获取全部数据
+        List<InformationQuery> result = baseMapper.selectInformationQueryPageTwo(vo);
+        if (result != null && result.size() != 0) {
+            //属于待审批、已审批状态任务Task信息,以及当前任务对应的副任务TaskParallel信息
+            Map<String, List<Task>> taskMaps = new HashMap<>();
+            Map<String, List<TaskParallel>> taskParallelMaps = new HashMap<>();
+            List<Long> informationIds = result.stream().filter(f -> f.getStatus().equals(1) || f.getStatus().equals(2)).map(InformationQuery::getId).collect(Collectors.toList());
+            String informationIdsStr = informationIds.stream().map(String::valueOf).collect(Collectors.joining(","));
+            if (org.apache.commons.lang.StringUtils.isNotEmpty(informationIdsStr)) {
+                List<Task> query = jdbcTemplate.query("SELECT id,form_data_id,process_instance_id,approval_type,status,batch,project_id,contract_id,create_time FROM u_task WHERE form_data_id IN(" + informationIdsStr + ") AND status IN (1, 2) AND is_deleted = 0", new BeanPropertyRowMapper<>(Task.class));
+                taskMaps = query.stream().collect(Collectors.groupingBy(Task::getFormDataId));
+                Set<String> processInstanceIds = query.stream().map(Task::getProcessInstanceId).map(id -> "'" + id + "'").collect(Collectors.toSet());
+                if (processInstanceIds.size() > 0) {
+                    List<TaskParallel> taskParallels = jdbcTemplate.query("select task_user, task_user_name, e_visa_status, status, process_instance_id, initiative,e_visa_content from u_task_parallel where is_deleted = 0 and process_instance_id in(" + org.apache.commons.lang.StringUtils.join(processInstanceIds, ",") + ")", new BeanPropertyRowMapper<>(TaskParallel.class));
+                    taskParallelMaps = taskParallels.stream().collect(Collectors.groupingBy(TaskParallel::getProcessInstanceId));
+                }
+            }
+
+            //校验关联的工序节点是否全都已审批
+            List<String> sjRecordIds = result.stream().map(InformationQuery::getSjRecordIds).filter(Objects::nonNull).collect(Collectors.toList());
+            Set<Long> ids = new HashSet<>();
+            for (String sjRecordId : sjRecordIds) {
+                List<Long> longs = Func.toLongList(sjRecordId);
+                ids.addAll(longs);
+            }
+            List<InformationQuery> informationQueries = new ArrayList<>();
+            if (ids.size() > 0) {
+                informationQueries = jdbcTemplate.query("SELECT * FROM u_information_query WHERE id IN(" + StringUtils.join(ids, ",") + ")", new BeanPropertyRowMapper<>(InformationQuery.class));
+            }
+
+            List<InformationQueryVO> voResult = JSONArray.parseArray(JSONObject.toJSONString(result), InformationQueryVO.class);
+            List<InformationQuery> finalInformationQueries = informationQueries;
+            Map<String, List<Task>> finalTaskMaps = taskMaps;
+            Map<String, List<TaskParallel>> finalTaskParallelMaps = taskParallelMaps;
+            voResult.forEach(vor -> {
+                if (ObjectUtil.isNotEmpty(vor.getCreateTime())) {
+                    vor.setStartTime(DateUtil.format(vor.getCreateTime(), "yyyy-MM-dd"));
+                }
+                vor.setTaskStatusStr(new Integer("0").equals(vor.getStatus()) ? "未上报" : new Integer("1").equals(vor.getStatus()) ? "待审批" : new Integer("2").equals(vor.getStatus()) ? "已审批" : "已废除");
+                try {
+                    String fileUserIdAndName = vor.getFileUserIdAndName();
+                    String[] fileUserIdAndNames = fileUserIdAndName.split(",");
+                    List<String> names = new ArrayList<>();
+                    for (String str : fileUserIdAndNames) {
+                        String[] strArr = str.split("-");
+                        if (!names.contains(strArr[1])) {
+                            names.add(strArr[1]);
+                        }
+                    }
+                    vor.setFileUserIdAndName(String.join(",", names));
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+
+                if (Arrays.asList("1,2".split(",")).contains(vor.getStatus().toString())) {
+                    List<Task> tasks = finalTaskMaps.getOrDefault(vor.getId().toString(), null);
+                    //获取创建时间最新的那条任务数据(存在多条任务指向同一个form_data_id,即一条资料多次上报,这里要获取最新的)
+                    if (tasks != null && tasks.size() > 0) {
+                        Optional<Task> maxTask = tasks.stream().max(Comparator.comparing(Task::getCreateTime));
+                        Task maxTaskObject = maxTask.get();
+                        List<TaskParallel> linkTasks = finalTaskParallelMaps.getOrDefault(maxTaskObject.getProcessInstanceId(), null);
+                        if (linkTasks != null && linkTasks.size() > 0) {
+                            this.integrationMethod(vor, linkTasks);
+                        }
+                        vor.setReportNumber(String.valueOf(tasks.get(0).getBatch()));
+                    }
+                }
+
+                //校验关联的工序节点是否全都已审批
+                if (StringUtils.isNotEmpty(vor.getSjRecordIds())) {
+                    List<String> recordIdList = Func.toStrList(vor.getSjRecordIds());
+                    List<InformationQuery> informationQueryList = finalInformationQueries.stream()
+                            .filter(f -> recordIdList.contains(f.getId().toString()))
+                            .collect(Collectors.toList());
+                    boolean isApprove = informationQueryList.stream()
+                            .allMatch(f -> f.getStatus().equals(2));
+                    vor.setIsApprove(isApprove);
+                }
+
+                //试验关联文件pdf
+                try {
+                    this.getMergePdf(vor);
+                } catch (FileNotFoundException e) {
+                    e.printStackTrace();
+                }
+            });
+
+            if (ObjectUtil.isEmpty(voResult)) {
+                return page.setRecords(null);
+
+            } else if (voResult.size() == 1) {
+                page.setRecords(voResult);
+                page.setTotal(voResult.size());
+                return page;
+
+            } else if (voResult.size() > 1) {
+                if (org.apache.commons.lang.StringUtils.isNotEmpty(vo.getIsFirst())) {
+
+                    long current = (page.getCurrent() - 1) * page.getSize();
+                    if (current < 0) {
+                        throw new IllegalArgumentException("当前页码不能小于0");
+                    }
+                    int currentNow = (int) (current / page.getSize() + 1);
+                    int pageSize = (int) page.getSize();
+                    int fromIndex = (currentNow - 1) * pageSize;
+                    int toIndex = Math.min(currentNow * pageSize, voResult.size());
+                    List<InformationQueryVO> subList = voResult.subList(fromIndex, toIndex);
+                    page.setRecords(subList);
+                    page.setTotal(voResult.size());
+                    return page;
+
+                } else {
+                    /**
+                     * 资料查询
+                     * @author liuyc
+                     * @date 2024年1月6日10:23:14
+                     * @description
+                     *   处理资料查询,page资料填报数据与节点顺序同步展示问题,查询出当前选择节点下所有资料填报信息,
+                     *   然后匹配节点位置信息,不分页查询page数据,分页查询会导致数据匹配异常,处理完匹配后,再分页返回。
+                     */
+                    //获取选择的节点下的所有子节点信息
+                    WbsTreeContract oldSelectedNode = BeanUtil.copyProperties(node, WbsTreeContract.class);
+                    List<WbsTreeContract> treeAll = this.getChildNodesZL(oldSelectedNode);
+                    if (treeAll == null) {
+                        treeAll = new ArrayList<>();
+                    }
+                    treeAll.add(0, oldSelectedNode);
+
+                    if (treeAll.size() > 1) {
+                        LinkedList<InformationQueryVO> resultData = new LinkedList<>();
+                        LinkedHashMap<Long, InformationQueryVO> dataMaps = voResult.stream()
+                                .collect(Collectors.toMap(InformationQueryVO::getWbsId, Function.identity(),
+                                        (oldValue, newValue) -> oldValue, LinkedHashMap::new));
+                        for (WbsTreeContract resultNode : treeAll) {
+                            InformationQueryVO queryVO = dataMaps.get(resultNode.getPKeyId());
+                            if (queryVO != null) {
+                                //按节点顺序插入Page数据
+                                resultData.add(queryVO);
+                            }
+                        }
+                        if (resultData.size() > 0) {
+                            long current = (page.getCurrent() - 1) * page.getSize();
+                            if (current < 0) {
+                                throw new IllegalArgumentException("当前页码不能小于0");
+                            }
+                            int currentNow = (int) (current / page.getSize() + 1);
+                            int pageSize = (int) page.getSize();
+                            int fromIndex = (currentNow - 1) * pageSize;
+                            int toIndex = Math.min(currentNow * pageSize, resultData.size());
+                            List<InformationQueryVO> subList = resultData.subList(fromIndex, toIndex);
+                            page.setRecords(subList);
+                            page.setTotal(resultData.size());
+                            return page;
+                        } else {
+                            return page.setRecords(null);
+                        }
+                    }
+                }
+            }
+        }
+        return page.setRecords(null);
+    }
+
+    private void lowestNodesRecursively(List<WbsTreeContractVO8> lowestNodes, Set<WbsTreeContractVO8> nodes, String contractId) {
+        if (nodes.size() > 0) {
+            Set<Long> ids = nodes.stream().map(WbsTreeContractVO8::getId).collect(Collectors.toSet());
+            if (ids.size() > 0) {
+                List<WbsTreeContractVO8> childNodes = jdbcTemplate.query("select a.p_key_id,a.id,(SELECT CASE WHEN COUNT(1) > 0 THEN 1 ELSE 0 END FROM m_wbs_tree_contract b WHERE b.parent_id = a.id AND b.contract_id = " + contractId + " AND b.status = 1 AND b.type = 1 AND b.is_deleted = 0) AS hasChildren from m_wbs_tree_contract a where a.type = 1 and a.status = 1 and a.is_deleted = 0 and a.contract_id = " + contractId + " and a.parent_id in(" + org.apache.commons.lang.StringUtils.join(ids, ",") + ")", new BeanPropertyRowMapper<>(WbsTreeContractVO8.class));
+                if (childNodes.size() > 0) {
+                    Set<WbsTreeContractVO8> lowestNode = childNodes.stream().filter(f -> f.getHasChildren().equals(0)).collect(Collectors.toSet());
+                    Set<WbsTreeContractVO8> noLowestNode = childNodes.stream().filter(f -> f.getHasChildren().equals(1)).collect(Collectors.toSet());
+                    //最底层节点
+                    if (lowestNode.size() > 0) {
+                        lowestNodes.addAll(lowestNode);
+                    }
+                    //非最底层节点
+                    if (noLowestNode.size() > 0) {
+                        this.lowestNodesRecursively(lowestNodes, noLowestNode, contractId);
+                    }
+                }
+            }
+        }
+    }
+
+    private void integrationMethod(InformationQueryVO vo, List<TaskParallel> linkList) {
+        for (TaskParallel taskPa : linkList) {
+            if (taskPa.getStatus() == 2 && ObjectUtil.isNotEmpty(taskPa.getEVisaStatus()) && taskPa.getEVisaStatus() == 1) {
+                taskPa.setEVisaStatus(2);
+            } else if (taskPa.getStatus() == 3 && taskPa.getTaskUser().equals(SecureUtil.getUserId().toString())) {
+                taskPa.setEVisaStatus(3);
+            } else if (ObjectUtil.isNotEmpty(taskPa.getEVisaStatus()) && taskPa.getEVisaStatus() == 99) {
+                taskPa.setEVisaStatus(999);
+            } else {
+                taskPa.setEVisaStatus(1);
+            }
+
+            String eVisaFailedInfo = "";
+            if (taskPa.getStatus() == 2 && ObjectUtil.isNotEmpty(taskPa.getEVisaStatus()) && taskPa.getEVisaStatus() == 2) {
+                if (taskPa.getEVisaContent().contains("请等待") && ObjectUtil.isEmpty(eVisaFailedInfo)) {
+                    eVisaFailedInfo = taskPa.getEVisaContent();
+                }
+            } else if (ObjectUtil.isNotEmpty(taskPa.getEVisaStatus()) && taskPa.getEVisaStatus() == 999) {
+                if (ObjectUtil.isEmpty(eVisaFailedInfo)) {
+                    eVisaFailedInfo = taskPa.getEVisaContent();
+                }
+            }
+            vo.setWaitingUserList(taskPa.getTaskUserName(), taskPa.getEVisaStatus());
+            vo.setEVisaFailedInfo(eVisaFailedInfo);
+        }
+    }
+
+    private void getMergePdf(InformationQueryVO vor) throws FileNotFoundException {
+        String pdfUrl = vor.getPdfUrl();
+        if (org.apache.commons.lang.StringUtils.isNotEmpty(vor.getEVisaPdfUrl())) {
+            //优先使用电签的pdf
+            pdfUrl = vor.getEVisaPdfUrl();
+            vor.setPdfUrl(pdfUrl);
+        }
+
+        if (org.apache.commons.lang.StringUtils.isNotEmpty(vor.getPdfTrialUrl()) || org.apache.commons.lang.StringUtils.isNotEmpty(vor.getPdfTrialUrlPosition())) {
+            //合并试验关联文件、试验工程部位信息的pdf
+            List<String> pdfList = new ArrayList<>();
+            //施工填报的原始pdf
+            pdfList.add(pdfUrl);
+            //关联试验的pdf
+            String pdfTrialUrl = vor.getPdfTrialUrl();
+            String pdfTrialUrlPosition = vor.getPdfTrialUrlPosition();
+            if (pdfTrialUrl != null) {
+                pdfList.add(pdfTrialUrl);
+            }
+            if (pdfTrialUrlPosition != null) {
+                pdfList.add(pdfTrialUrlPosition);
+            }
+            //合并
+            if (pdfList.size() >= 2) {
+                String file_path = ParamCache.getValue(CommonConstant.SYS_LOCAL_URL);
+                Long id = SnowFlakeUtil.getId();
+                String trialPdf = file_path + "/pdf/" + id + ".pdf";
+                File trialPdf2 = ResourceUtil.getFile(trialPdf);
+                if (trialPdf2.exists()) {
+                    trialPdf2.delete();
+                }
+                //合并当前所有选择的试验pdf
+                FileUtils.mergePdfPublicMethods(pdfList, trialPdf);
+                BladeFile bladeFile = this.newIOSSClient.uploadFile(id + ".pdf", trialPdf);
+                if (bladeFile != null && ObjectUtils.isNotEmpty(bladeFile.getLink())) {
+                    vor.setPdfUrl(bladeFile.getLink());
+                }
+            }
+        }
+    }
+
+    private List<WbsTreeContract> getChildNodesZL(WbsTreeContract obj) {
+        if (obj != null) {
+            List<WbsTreeContract> wbsTreeContracts = Collections.singletonList(obj);
+            List<WbsTreeContract> result = new ArrayList<>();
+            this.recursionGetChildNodesZL(wbsTreeContracts, result, obj.getContractId());
+            if (result.size() > 0) {
+                return result;
+            }
+        }
+        return null;
+    }
+
+    private void recursionGetChildNodesZL(List<WbsTreeContract> list, List<WbsTreeContract> result, String contractId) {
+        List<Long> ids = list.stream().map(WbsTreeContract::getId).collect(Collectors.toList());
+        if (ids.size() > 0) {
+            //构建以parent_id为key的Map
+            Map<Long, List<WbsTreeContract>> queryMap = jdbcTemplate.query(
+                    "SELECT id,p_key_id,parent_id,sort,create_time," +
+                            "IFNULL(if(LENGTH (trim(full_name)) > 0, full_name, node_name), node_name) AS fullName " +
+                            "FROM m_wbs_tree_contract WHERE type = 1 AND status = 1 AND is_deleted = 0 " +
+                            "AND parent_id IN (" + org.apache.commons.lang.StringUtils.join(ids, ",") + ") " +
+                            "AND contract_id = " + contractId + " ORDER BY sort,fullName,create_time",
+                    rs -> {
+                        Map<Long, List<WbsTreeContract>> map = new LinkedHashMap<>();
+                        while (rs.next()) {
+                            WbsTreeContract item = new WbsTreeContract();
+                            item.setId(rs.getLong("id"));
+                            item.setPKeyId(rs.getLong("p_key_id"));
+                            item.setParentId(rs.getLong("parent_id"));
+                            item.setSort(rs.getInt("sort"));
+                            item.setFullName(rs.getString("fullName"));
+                            item.setCreateTime(rs.getTime("create_time"));
+                            Long parentId = item.getParentId();
+                            if (!map.containsKey(parentId)) {
+                                map.put(parentId, new ArrayList<>());
+                            }
+                            map.get(parentId).add(item);
+                        }
+                        return map;
+                    });
+            //按照ids的顺序加入result
+            for (Long id : ids) {
+                if (queryMap != null && queryMap.containsKey(id)) {
+                    List<WbsTreeContract> nodes = queryMap.get(id);
+                    result.addAll(nodes);
+                    recursionGetChildNodesZL(nodes, result, contractId);
+                }
+            }
+        }
+    }
+
+
 }

+ 312 - 0
blade-service/blade-user/src/main/java/org/springblade/system/user/util/FileUtils.java

@@ -0,0 +1,312 @@
+package org.springblade.system.user.util;
+
+import com.drew.imaging.ImageMetadataReader;
+import com.drew.imaging.ImageProcessingException;
+import com.drew.metadata.Metadata;
+import com.drew.metadata.MetadataException;
+import com.drew.metadata.exif.ExifIFD0Directory;
+import com.itextpdf.text.Document;
+import com.itextpdf.text.pdf.PdfCopy;
+import com.itextpdf.text.pdf.PdfReader;
+import org.apache.commons.lang.StringUtils;
+import org.apache.poi.ss.usermodel.ClientAnchor;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.util.Units;
+import org.springblade.common.utils.CommonUtil;
+import org.springblade.common.vo.DataVO;
+import org.springblade.core.tool.utils.IoUtil;
+
+import javax.imageio.IIOImage;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.ImageWriter;
+import javax.servlet.http.HttpServletResponse;
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+import java.awt.image.AffineTransformOp;
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.net.URLEncoder;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+public class FileUtils {
+
+    public static void batchDownloadFileToZip(List<String> urls, HttpServletResponse response) {
+        // 设置压缩流:直接写入response,实现边压缩边下载
+        ZipOutputStream zipos = null;
+        // 循环将文件写入压缩流
+        DataOutputStream os = null;
+        try {
+
+            // 响应头的设置
+            response.reset();
+            response.setCharacterEncoding("utf-8");
+            response.setContentType("multipart/form-data");
+            // 设置压缩包的名字
+            // 解决不同浏览器压缩包名字含有中文时乱码的问题
+            String downloadName = "附件-" + System.currentTimeMillis() + ".zip";
+            response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(downloadName, "UTF-8"));
+
+
+            try {
+                zipos = new ZipOutputStream(new BufferedOutputStream(response.getOutputStream()));
+                // 设置压缩方法
+                zipos.setMethod(ZipOutputStream.DEFLATED);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+
+            if (zipos == null) {
+                return;
+            }
+
+            InputStream inputStream = null;
+
+            for (String url : urls) {
+                if (StringUtils.isNotEmpty(url)) {
+                    try {
+                        String fileName = null, symbol = "";
+                        if (url.contains("@@@")) {
+                            String[] array = url.split("@@@");
+                            url = array[0];
+                            fileName = array[1];
+                            symbol = url.substring(url.lastIndexOf("."));
+                        }
+
+                        //获取文件流
+                        inputStream = CommonUtil.getOSSInputStream(url);
+                        //转换
+                        byte[] bytes = CommonUtil.InputStreamToBytes(inputStream);
+
+                        if (StringUtils.isEmpty(fileName)) {
+                            fileName = url.substring(url.lastIndexOf("/") + 1);
+                            symbol = "";
+                        }
+                        zipos.putNextEntry(new ZipEntry(fileName + symbol));
+
+                        os = new DataOutputStream(zipos);
+                        os.write(bytes);
+                        zipos.closeEntry();
+
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                    } finally {
+                        IoUtil.closeQuietly(inputStream);
+                    }
+                }
+            }
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            IoUtil.closeQuietly(os);
+            IoUtil.closeQuietly(zipos);
+        }
+    }
+
+    /**
+     * 图片缩放、压缩、旋转处理
+     *
+     * @param imageData
+     * @return
+     * @throws IOException
+     * @throws ImageProcessingException
+     * @throws MetadataException
+     */
+    public static byte[] compressImage(byte[] imageData) throws IOException, ImageProcessingException, MetadataException {
+        // 读取原始图像(处理旋转问题)
+        Metadata metadata = ImageMetadataReader.readMetadata(new ByteArrayInputStream(imageData));
+        if (metadata.containsDirectoryOfType(ExifIFD0Directory.class)) {
+            ExifIFD0Directory exifIFD0Directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
+            if (exifIFD0Directory.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) {
+                // 获取 Orientation 标签的值
+                int orientation = exifIFD0Directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);
+                // 需要旋转图片
+                BufferedImage originalImage = ImageIO.read(new ByteArrayInputStream(imageData));
+                AffineTransform transform = new AffineTransform();
+                if (orientation == 6) {
+                    transform.rotate(Math.PI / 2, originalImage.getWidth() / 2, originalImage.getHeight() / 2);
+                } else if (orientation == 8) {
+                    transform.rotate(-Math.PI / 2, originalImage.getWidth() / 2, originalImage.getHeight() / 2);
+                }
+                AffineTransformOp op = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR);
+                originalImage = op.filter(originalImage, null);
+                ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                ImageIO.write(originalImage, "jpg", baos);
+                imageData = baos.toByteArray();
+            }
+        }
+
+        // 缩放图像
+        String formatName = "JPEG";
+        ByteArrayInputStream bais = new ByteArrayInputStream(imageData);
+        BufferedImage originalImage = ImageIO.read(bais);
+        long sizeLimit = 366912; //358KB
+        int width = 768;
+        int height = 1024;
+        Image scaledImage = originalImage.getScaledInstance(width, height, Image.SCALE_SMOOTH);
+        BufferedImage resizedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+        resizedImage.getGraphics().drawImage(scaledImage, 0, 0, null);
+
+        // 压缩图像
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        ImageIO.write(resizedImage, formatName, baos);
+
+        if (baos.size() <= sizeLimit) {
+            // 图片大小已经小于等于目标大小,直接返回原始数据
+            return baos.toByteArray();
+        }
+
+        float quality = 0.9f; // 初始化压缩质量
+        int retries = 10; // 最多尝试 10 次
+
+        while (baos.size() > sizeLimit && retries > 0) {
+            // 压缩图像并重新计算压缩质量
+            byte[] data = baos.toByteArray();
+            bais = new ByteArrayInputStream(data);
+            BufferedImage compressedImage = ImageIO.read(bais);
+            baos.reset();
+
+            ImageWriter writer = null;
+            Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(formatName);
+            if (writers.hasNext()) {
+                writer = writers.next();
+            } else {
+                throw new IllegalArgumentException("Unsupported image format: " + formatName);
+            }
+
+            ImageWriteParam writeParam = writer.getDefaultWriteParam();
+            writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+            writeParam.setCompressionQuality(quality);
+
+            writer.setOutput(ImageIO.createImageOutputStream(baos));
+            writer.write(null, new IIOImage(compressedImage, null, null), writeParam);
+            writer.dispose();
+
+            float ratio = sizeLimit * 1.0f / baos.size();
+            quality *= Math.sqrt(ratio);
+            retries--;
+        }
+
+        return baos.toByteArray();
+    }
+
+    /**
+     * 设置图片的定位、大小和合并单元格处理
+     *
+     * @param sheet  工作表对象
+     * @param anchor 锚点对象,表示图片的位置和大小
+     * @param dataVO 数据值对象,包含插入图片的单元格坐标
+     */
+    public static void imageOrientation(Sheet sheet, ClientAnchor anchor, DataVO dataVO) {
+        // 设置图片距左边和上边的偏移量
+        anchor.setDx1(Units.pixelToEMU(5));
+        anchor.setDy1(Units.pixelToEMU(5));
+        // 设置图片右下角所在单元格的行列号与左上角一致
+        anchor.setCol2(anchor.getCol1());
+        anchor.setRow2(anchor.getRow1());
+        // 判断当前单元格是否属于一个合并单元格
+        int k = getMergedRegionIndex(sheet, CommonUtil.join(dataVO.getX(), dataVO.getY(), dataVO.getX(), dataVO.getY(), ","));
+        if (k > -1) {
+            // 如果是合并单元格,则锚点第一坐标设置为合并区左上角单元格坐标,第二坐标设置为合并区右下角单元格坐标
+            CellRangeAddress ca = sheet.getMergedRegion(k);
+            anchor.setCol1(ca.getFirstColumn());
+            anchor.setRow1(ca.getFirstRow());
+            anchor.setCol2(ca.getLastColumn());
+            anchor.setRow2(ca.getLastRow());
+        }
+        // 计算图片宽度和高度的像素值,并将像素值转换成EMUs(English Metric Units)
+        int dx = (int) (sheet.getColumnWidthInPixels(anchor.getCol2()) + 3);
+        int dy = Units.pointsToPixel(sheet.getRow(anchor.getRow2()).getHeightInPoints()) - 5;
+        anchor.setDx2(Units.pixelToEMU(dx));
+        anchor.setDy2(Units.pixelToEMU(dy));
+
+        // 禁止旋转
+        anchor.setAnchorType(ClientAnchor.AnchorType.DONT_MOVE_AND_RESIZE);
+    }
+
+    /**
+     * 获取指定单元格所在的合并单元格的下标
+     *
+     * @param sheet  工作表对象
+     * @param coords 单元格坐标字符串,格式为"左上角行号,左上角列号,右下角行号,右下角列号"
+     * @return 返回指定单元格所在的合并单元格下标,如果不存在则返回-1
+     */
+    public static int getMergedRegionIndex(Sheet sheet, String coords) {
+        // 遍历所有合并单元格
+        for (int i = 0; i < sheet.getNumMergedRegions(); i++) {
+            CellRangeAddress ca = sheet.getMergedRegion(i);
+            int firstColumn = ca.getFirstColumn();
+            int lastColumn = ca.getLastColumn();
+            int firstRow = ca.getFirstRow();
+            int lastRow = ca.getLastRow();
+            // 解析指定单元格坐标字符串
+            Matcher mu = CommonUtil.matcher("(\\d{1,3}),(\\d{1,3}),(\\d{1,3}),(\\d{1,3})", coords);
+            // 判断是否有重叠部分,如果有则返回对应的合并单元格下标
+            List<Integer[]> corners = Arrays.asList(new Integer[]{firstColumn, firstRow}, new Integer[]{lastColumn, firstRow}, new Integer[]{firstColumn, lastRow}, new Integer[]{lastColumn, lastRow});
+            if (mu.find()) {
+                int firstColumn2 = mu.group(1) == null ? 0 : Integer.parseInt(mu.group(1));
+                int lastColumn2 = mu.group(3) == null ? 0 : Integer.parseInt(mu.group(3));
+                int firstRow2 = mu.group(2) == null ? 0 : Integer.parseInt(mu.group(2));
+                int lastRow2 = mu.group(4) == null ? 0 : Integer.parseInt(mu.group(4));
+                for (Integer[] corner : corners) {
+                    if (firstColumn2 <= corner[0] && corner[0] <= lastColumn2 && firstRow2 <= corner[1] && corner[1] <= lastRow2) {
+                        return i;
+                    }
+                }
+            }
+        }
+        // 如果没有重叠部分,则返回-1
+        return -1;
+    }
+
+    /**
+     * 合并方法
+     */
+    public static void mergePdfPublicMethods(List<String> urlList, String localImgUrl) {
+        PdfReader reader = null;
+
+        Document doc = new Document();
+        PdfCopy pdfCopy = null;
+        try {
+            pdfCopy = new PdfCopy(doc, new FileOutputStream(localImgUrl));
+            int pageCount;
+            doc.open();
+
+            for (String urlStr : urlList) {
+                try {
+                    //获取OSS文件输入流
+                    reader = new PdfReader(CommonUtil.getOSSInputStream(urlStr));
+
+                    pageCount = reader.getNumberOfPages();
+
+                    for (int i = 0; i < pageCount; ++i) {
+                        int is = i + 1;
+                        pdfCopy.addPage(pdfCopy.getImportedPage(reader, is));
+                    }
+                } catch (Exception e) {
+                    e.printStackTrace();
+                } finally {
+                    if (reader != null) {
+                        reader.close();
+                    }
+                }
+            }
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            if (pdfCopy != null) {
+                pdfCopy.flush();
+                pdfCopy.close();
+            }
+            doc.close();
+        }
+    }
+}