src/main/java/com/product/file/service/FileManagerService.java
@@ -1,9 +1,13 @@
package com.product.file.service;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.product.common.lang.StringUtils;
import com.product.core.cache.util.RedisUtil;
import com.product.core.config.CoreConst;
import com.product.core.config.Global;
import com.product.core.connection.ConnectionManager;
import com.product.core.dao.BaseDao;
@@ -16,22 +20,26 @@
import com.product.core.spring.context.SpringMVCContextHolder;
import com.product.file.config.CmnConst;
import com.product.file.config.FileCode;
import com.product.file.util.AsposeUtil;
import com.product.file.util.CreateDocumentIndexThread;
import com.product.file.util.FileUtil;
import com.product.file.util.FileUtils;
import com.product.module.sys.entity.SystemUser;
import com.product.tool.table.enums.FieldType;
import com.product.util.BaseUtil;
import com.product.util.http.HttpRequestUtil;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.text.SimpleDateFormat;
import java.util.*;
@@ -52,6 +60,15 @@
   public BaseDao baseDao;
   @Autowired
   FileUtils fileUtils;
   /**
    *  标记文件已被修改
    * @param uuid
    * @param userId
    */
   public void signUpdateAttachment(String uuid, int userId) {
      baseDao.executeUpdate("UPDATE product_sys_attachments SET updated_by = ?, updated_utc_datetime = NOW() WHERE uuid = ?", new Object[] {userId, uuid});
   }
   /**
    * 获取静态资源
@@ -268,6 +285,134 @@
      }
   }
    /**
     * 集群文件同步方法-接收
     * @param rpe
     * @return
     */
    public void clusterFileSyncAccept(RequestParameterEntity rpe) throws IOException {
        List<String> ipList = RedisUtil.getSet(CoreConst.SYSTEM_IP_PORT_KEY, String.class);
        String curRequestIp = rpe.getIp();
        boolean checkFlag = false;
        for (String ipInfo : ipList) {
            if (!StringUtils.isEmpty(ipInfo) && ipInfo.contains(":") && curRequestIp.equals(ipInfo.split(":")[0])) {
                checkFlag = true;
                break;
            }
        }
        if (!checkFlag) {
            throw new BaseException(FileCode.CLUSTER_FILE_SYNC_CHECK_FAIL);
        }
        Map<Object, Object> otherMap = rpe.getOther();
        Map<String, File> fileMap = rpe.getFiles();
        File aimFile = new File(Global.getSystemConfig("local.dir", "") + File.separator + otherMap.get("relativePath"));
        for (Map.Entry<String, File> entry : fileMap.entrySet()) {
            File file = entry.getValue();
            Files.copy(file.toPath(), aimFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
        }
    }
    /**
     * 集群文件同步方法-请求调用
     * @param file              同步的文件
     * @param fileName          文件名称,保存的文件名称,就是时间戳+2位随机字符串
     * @param relativePath      相对路径
     * @param attachmentUUID    附近表uuid
     * @param preLogFse         之前的日志fse
     */
    public void clusterFileSyncRequest(File file, String fileName, String relativePath, String attachmentUUID, FieldSetEntity preLogFse) {
        try {
            if (file == null || StringUtils.isEmpty(fileName)) {
                return;
            }
            List<String> ipList = RedisUtil.getSet(CoreConst.SYSTEM_IP_PORT_KEY, String.class);
            String curIpInfo = Global.getSystemConfig(CoreConst.SYSTEM_NAME, "");
            for (String ipInfo : ipList) {
                // 排除当前
                if (curIpInfo.equals(ipInfo)) {
                   continue;
                }
                if (!StringUtils.isEmpty(ipInfo) && ipInfo.contains(":")) {
                    String url = String.format("http://%s/lx/api/fileManager/cluster-file-sync/v1", ipInfo);
                    RequestBody requestBody = new MultipartBody.Builder()
                            .setType(MultipartBody.FORM)
                            // 添加文件字段
                            .addFormDataPart("file", fileName, RequestBody.create(MediaType.parse("application/octet-stream"), file))
                            .addFormDataPart("title", fileName)
                            .addFormDataPart("relativePath", relativePath)
                            .build();
                    JSONObject result = HttpRequestUtil.request(url, "POST", "multipart/form-data", requestBody);
                    SpringMVCContextHolder.getSystemLogger().error("[6ctest]:" + result.toString());
                    // 记录日志
                    recordClusterSyncFileLog(result, attachmentUUID, curIpInfo, ipInfo, preLogFse);
                }
            }
        } catch (Exception e) {
            throw new BaseException(FileCode.CLUSTER_FILE_SYNC_FAIL);
        }
    }
    public void clusterFileSyncRequest(File file, String fileName, String relativePath, String attachmentUUID) {
        clusterFileSyncRequest(file, fileName, relativePath, attachmentUUID, null);
    }
    /**
     * 记录集群同步文件日志
     * @param result            结果,-1-失败,1-成功,2-已重新处理
     * @param attachmentUUID    附件uuid
     * @param sourceIpInfo      请求地址信息
     * @param targetIpInfo      目标地址信息
     * @param preLogFse         之前日志fse
     */
    private void recordClusterSyncFileLog(JSONObject result, String attachmentUUID, String sourceIpInfo, String targetIpInfo, FieldSetEntity preLogFse) {
        FieldSetEntity logFse = new FieldSetEntity();
        logFse.setTableName(CmnConst.PRODUCT_SYS_CLUSTER_SYNC_FILE_LOG);
        if (!"200".equals(result.getString("code"))) {
            logFse.setValue(CmnConst.ERROR_INFO, result.getString("msg"));
            logFse.setValue(CmnConst.RESULT, -1);
            logFse.setValue(CmnConst.NEED_RETRY, 1);
        } else {
            logFse.setValue(CmnConst.RESULT, 1);
            logFse.setValue(CmnConst.NEED_RETRY, -1);
        }
        if (!FieldSetEntity.isEmpty(preLogFse)) {
            logFse.setValue(CmnConst.RETRY_COUNT, preLogFse.getInteger(CmnConst.RETRY_COUNT) == null ? 1 : (preLogFse.getInteger(CmnConst.RETRY_COUNT) + 1));
            logFse.setValue(CmnConst.PRE_UUID, preLogFse.getUUID());
        } else {
            logFse.setValue(CmnConst.RETRY_COUNT, 0);
        }
        logFse.setValue(CmnConst.ATTACHMENT_UUID, attachmentUUID);
        logFse.setValue(CmnConst.SOURCE_INFO, sourceIpInfo);
        logFse.setValue(CmnConst.TARGET_INFO, targetIpInfo);
        logFse.setValue(CmnConst.CREATED_UTC_DATETIME, new Date());
        SystemUser curUser = SpringMVCContextHolder.getCurrentUser();
        logFse.setValue(CmnConst.CREATED_BY, curUser == null ? -1 : curUser.getUser_id());
        baseDao.saveFieldSetEntity(logFse);
    }
    /**
     * 定时任务触发-扫描集群同步文件日志记录表,重试未达最大次数的错误日志
     */
    public void retryClusterSyncFileFailLog() {
        // 最大重试次数
        int maxRetryCount = 3;
        // 当前地址信息
        String curIpInfo = Global.getSystemConfig(CoreConst.SYSTEM_NAME, "");
        DataTableEntity waitRetryDte = baseDao.listTable(CmnConst.PRODUCT_SYS_CLUSTER_SYNC_FILE_LOG, "result=-1 AND retry_count<? AND need_retry=1 AND source_info=?", new Object[]{maxRetryCount, curIpInfo});
        for (int i = 0; i < waitRetryDte.getRows(); i++) {
            FieldSetEntity waitRetryFse = waitRetryDte.getFieldSetEntity(i);
            FieldSetEntity attachmentFse = baseDao.getFieldSetEntity(CmnConst.PRODUCT_SYS_ATTACHMENTS, waitRetryFse.getString(CmnConst.ATTACHMENT_UUID), false);
            String fileName = attachmentFse.getString("attachment_title");
            String relativePath = attachmentFse.getString("attachment_url") + File.separator + fileName;
            File aimFile = new File(Global.getSystemConfig("local.dir", "") + File.separator + relativePath);
            clusterFileSyncRequest(aimFile, fileName, relativePath, attachmentFse.getUUID(), waitRetryFse);
            waitRetryFse.setValue(CmnConst.RESULT, 2);
            waitRetryFse.setValue(CmnConst.NEED_RETRY, -1);
            baseDao.saveFieldSetEntity(waitRetryFse);
        }
    }
   /**
    * 上传文件到本地服务器
    * 禁止修改任何逻辑!!!!!!
@@ -303,7 +448,7 @@
      Object value;
      String fileNames;
      FieldSetEntity fieldFse;
      File tempFile;
      File tempFile = null;
      File localTempFile = null;
      long fileLength = 0;
@@ -319,10 +464,11 @@
      }
      String uuids = "";
        String fileFinalName = "";
      for (Map.Entry<Object, Object> entry : map.entrySet()) {
         fieldFse = fieldMetaEntity.getFieldMeta(fieldName);
         if (fieldFse == null || !CmnConst.ATTACHMENT_TYPE.equals(fieldFse.getString(CmnConst.FIELD_TYPE))) {
            fse.setValue(entry.getKey().toString(), null);
            if (fieldFse == null || !Arrays.asList(CmnConst.ATTACHMENT_TYPE, "file-image", FieldType.FILE_ATTACHMENT.getDictValue()).contains(fieldFse.getString(CmnConst.FIELD_TYPE))) {
                fse.setValue(entry.getKey().toString(), null);
            continue;
         }
         // 已经判定为附件,进行操作
@@ -353,65 +499,83 @@
               }
            }
            final String fileFinalName = FileUtils.uploadFile(tempFile, templateType, clientUuid);
                boolean saveInDbFlag = FieldType.FILE_ATTACHMENT.getDictValue().equals(fieldFse.getString("field_type"));
                if (!saveInDbFlag) {
                    fileFinalName = FileUtils.uploadFile(tempFile, templateType, clientUuid);
                } else {
                    fileFinalName = System.currentTimeMillis() + RandomUtil.randomString(2);
                }
            // 记录附件信息到数据库
            logger.info("正在记录附件信息到数据库...");
            FieldSetEntity dictFse = baseDao.getFieldSetEntityByFilter(CmnConst.PRODUCT_SYS_DICT,
                  new String[]{CmnConst.UUID, CmnConst.DICT_VALUE}, "lower( " + CmnConst.DICT_VALUE + ")=lower(?) and dict_name='upload_file_format' and is_used=1", new Object[]{tail}, false, "");
            FieldSetEntity attachmentFse = new FieldSetEntity();
            attachmentFse.setTableName(CmnConst.PRODUCT_SYS_ATTACHMENTS);
            String fileType;
            if (dictFse != null && !StringUtils.isEmpty(dictFse.getString(CmnConst.UUID))) {
               attachmentFse.setValue(CmnConst.ATTACHMENT_TYPE_UUID, dictFse.getUUID());
               fileType = dictFse.getString(CmnConst.DICT_VALUE);
            } else {
               fileType = tail;
               attachmentFse.setValue(CmnConst.ATTACHMENT_TYPE_UUID, tail);
            }
            //允许编辑
            int allowEdit = ("," + Global.getSystemConfig("can.web.online.edit", "doc,docx,xls,xlsx.ppt,pptx,wps,cvs,wps,wpt,et,eet") + ",").indexOf("," + fileType + ",") != -1 ? 1 : 0;
            attachmentFse.setValue(CmnConst.OPT_FLAT, 2);
            attachmentFse.setValue(CmnConst.FILE_NAME, fileName);
            attachmentFse.setValue(CmnConst.ATTACHMENT_TITLE, fileFinalName);
            attachmentFse.setValue(CmnConst.CLIENT_UUID, clientUuid);
            attachmentFse.setValue(CmnConst.VIEW_ONLINE_SIGN, viewOnlineSign);
            attachmentFse.setValue(CmnConst.ATTACHMENT_DATA_TABLE, fse.getTableName());
            attachmentFse.setValue(CmnConst.ATTACHMENT_DATA_FIELD, fieldName);
            attachmentFse.setValue(CmnConst.ATTACHMENT_URL, dir.replaceAll("\\\\", "/"));
            attachmentFse.setValue(CmnConst.ENCRPT_SIGN, Global.getPropertyToBoolean("file.encrypt", "true") ? 1 : 0);
            attachmentFse.setValue(CmnConst.ATTACHMENT_SIZE, fileLength);
            attachmentFse.setValue(CmnConst.UPLOAD_SIGN, needUpload2FileServerFlag ? 1 : 0);
            attachmentFse.setValue(CmnConst.FUNCTION_UUID, fse.getString(CmnConst.FUNCTION_UUID));
            attachmentFse.setValue(CmnConst.ATTACHMENT_CONTAINER, fse.getString(CmnConst.ATTACHMENT_CONTAINER));
            attachmentFse.setValue(CmnConst.ATTACHMENT_DOMAIN, fse.getString(CmnConst.ATTACHMENT_DOMAIN));
            attachmentFse.setValue(CmnConst.MODULE_UUID, fse.getString(CmnConst.MODULE_UUID));
            //luoxin 未获取当前人id  就直接覆1
            try {
               attachmentFse.setValue(CmnConst.CREATED_BY, SpringMVCContextHolder.getCurrentUser().getUser_id());
            } catch (Exception e) {
               e.getStackTrace();
               attachmentFse.setValue(CmnConst.CREATED_BY, 1);
            }
                logger.info("正在记录附件信息到数据库...");
                FieldSetEntity dictFse = baseDao.getFieldSetEntityByFilter(CmnConst.PRODUCT_SYS_DICT,
                        new String[]{CmnConst.UUID, CmnConst.DICT_VALUE}, "lower( " + CmnConst.DICT_VALUE + ")=lower(?) and dict_name='upload_file_format' and is_used=1", new Object[]{tail}, false, "");
                FieldSetEntity attachmentFse = new FieldSetEntity();
                attachmentFse.setTableName(CmnConst.PRODUCT_SYS_ATTACHMENTS);
                String fileType;
                if (dictFse != null && !StringUtils.isEmpty(dictFse.getString(CmnConst.UUID))) {
                    attachmentFse.setValue(CmnConst.ATTACHMENT_TYPE_UUID, dictFse.getUUID());
                    fileType = dictFse.getString(CmnConst.DICT_VALUE);
                } else {
                    fileType = tail;
                    attachmentFse.setValue(CmnConst.ATTACHMENT_TYPE_UUID, tail);
                }
                if (saveInDbFlag) {
                    // 判断附件字段的类型为FileAttachment时,要把文档内容放到附件表中的file_content字段中
                    try {
                        attachmentFse.setValue(CmnConst.FILE_CONTENT, com.product.common.io.FileUtils.readFileToByteArray(tempFile));
                    } catch (Exception e) {
                        throw new BaseException(FileCode.FILE_TRANSFER_BYTE_ARRAY_FAIL);
                    }
                }
                //允许编辑
                int allowEdit = ("," + Global.getSystemConfig("can.web.online.edit", "doc,docx,xls,xlsx.ppt,pptx,wps,cvs,wps,wpt,et,eet") + ",").indexOf("," + fileType + ",") != -1 ? 1 : 0;
                attachmentFse.setValue(CmnConst.OPT_FLAT, 2);
                attachmentFse.setValue(CmnConst.FILE_NAME, fileName);
                attachmentFse.setValue(CmnConst.ATTACHMENT_TITLE, fileFinalName);
                attachmentFse.setValue(CmnConst.CLIENT_UUID, clientUuid);
                attachmentFse.setValue(CmnConst.VIEW_ONLINE_SIGN, viewOnlineSign);
                attachmentFse.setValue(CmnConst.ATTACHMENT_DATA_TABLE, fse.getTableName());
                attachmentFse.setValue(CmnConst.ATTACHMENT_DATA_FIELD, fieldName);
                attachmentFse.setValue(CmnConst.ATTACHMENT_URL, dir.replaceAll("\\\\", "/"));
                attachmentFse.setValue(CmnConst.ENCRPT_SIGN, Global.getPropertyToBoolean("file.encrypt", "true") ? 1 : 0);
                attachmentFse.setValue(CmnConst.ATTACHMENT_SIZE, fileLength);
                attachmentFse.setValue(CmnConst.UPLOAD_SIGN, needUpload2FileServerFlag ? 1 : 0);
                attachmentFse.setValue(CmnConst.FUNCTION_UUID, fse.getString(CmnConst.FUNCTION_UUID));
                attachmentFse.setValue(CmnConst.ATTACHMENT_CONTAINER, fse.getString(CmnConst.ATTACHMENT_CONTAINER));
                attachmentFse.setValue(CmnConst.ATTACHMENT_DOMAIN, fse.getString(CmnConst.ATTACHMENT_DOMAIN));
                attachmentFse.setValue(CmnConst.MODULE_UUID, fse.getString(CmnConst.MODULE_UUID));
                //luoxin 未获取当前人id  就直接覆1
                try {
                    attachmentFse.setValue(CmnConst.CREATED_BY, SpringMVCContextHolder.getCurrentUser().getUser_id());
                } catch (Exception e) {
                    e.getStackTrace();
                    attachmentFse.setValue(CmnConst.CREATED_BY, 1);
                }
            attachmentFse.setValue(CmnConst.CREATED_UTC_DATETIME, new Date());
            baseDao.saveFieldSetEntity(attachmentFse);
            logger.info("记录成功");
            String uuid = attachmentFse.getUUID();
            FieldSetEntity fseIndex = new FieldSetEntity();
            fseIndex.setTableName("fseIndex");
            fseIndex.setValue("function_uuid", fse.getString("function_uuid"));
            fseIndex.setValue("attachment_uuid", uuid);
            CreateDocumentIndexThread.getInstance().appendAttaInfo(fseIndex);
            fse.setValue(uuid, fileName);
            fse.setValue(entry.getKey().toString(), uuid);
            fse.setValue(entry.getKey().toString() + "_edit", allowEdit);
            fse.setValue(entry.getKey().toString() + "_type", fileType);
            if (!StringUtils.isEmpty(uuids)) {
               uuids += ",";
            }
            uuids += uuid;
                attachmentFse.setValue(CmnConst.CREATED_UTC_DATETIME, new Date());
                baseDao.saveFieldSetEntity(attachmentFse);
                logger.info("记录成功");
                String uuid = attachmentFse.getUUID();
                FieldSetEntity fseIndex = new FieldSetEntity();
                fseIndex.setTableName("fseIndex");
                fseIndex.setValue("function_uuid", fse.getString("function_uuid"));
                fseIndex.setValue("attachment_uuid", uuid);
                CreateDocumentIndexThread.getInstance().appendAttaInfo(fseIndex);
                fse.setValue(uuid, fileName);
                fse.setValue(entry.getKey().toString(), uuid);
                fse.setValue(entry.getKey().toString() + "_edit", allowEdit);
                fse.setValue(entry.getKey().toString() + "_type", fileType);
                if (!StringUtils.isEmpty(uuids)) {
                    uuids += ",";
                }
                uuids += uuid;
                // 集群布署本地存储时,附件信息要同步到其它服务器上
                if (!saveInDbFlag && !needUpload2FileServerFlag) {
                    File curFile = new File(Global.getSystemConfig("local.dir", "") + File.separator + dir + File.separator + fileFinalName);
                    clusterFileSyncRequest(curFile, fileFinalName, dir + File.separator + fileFinalName, attachmentFse.getUUID());
                }
         }
         logger.info("正在回写uuid...");
         System.out.println(uuids);
@@ -419,7 +583,7 @@
      }
      return fse;
   }
   /**
    *    根据附件表数据生成文档索引
    */
@@ -427,17 +591,17 @@
      // 遍历附件表
      DataTableEntity dtAttachment = baseDao.listTable(CmnConst.PRODUCT_SYS_ATTACHMENTS);
      if (BaseUtil.dataTableIsEmpty(dtAttachment)) {
         return ;
         return;
      }
      for (int i = 0; i < dtAttachment.getRows(); i++) {
         // 获取附件信息,并判断是否有表和字段
         FieldSetEntity fseAttachment = dtAttachment.getFieldSetEntity(i);
         if(BaseUtil.strIsNull(fseAttachment.getString("attachment_data_field")) || BaseUtil.strIsNull(fseAttachment.getString("attachment_data_field"))) {
         if (BaseUtil.strIsNull(fseAttachment.getString("attachment_data_field")) || BaseUtil.strIsNull(fseAttachment.getString("attachment_data_field"))) {
            continue;
         }
         // 查询原数据,有原数据再生成文档检索
         FieldSetEntity fseRecord = baseDao.getFieldSetEntityByFilter(fseAttachment.getString("attachment_data_table"), fseAttachment.getString("attachment_data_field") + " LIKE ?", new Object[] {"%"+fseAttachment.getUUID()+"%"}, false);
         if(fseRecord != null) {
         FieldSetEntity fseRecord = baseDao.getFieldSetEntityByFilter(fseAttachment.getString("attachment_data_table"), fseAttachment.getString("attachment_data_field") + " LIKE ?", new Object[]{"%" + fseAttachment.getUUID() + "%"}, false);
         if (fseRecord != null) {
            // 生成文档检索信息
            FieldSetEntity fseIndex = new FieldSetEntity();
            fseIndex.setTableName("fseIndex");
@@ -446,6 +610,146 @@
            CreateDocumentIndexThread.getInstance().appendAttaInfo(fseIndex);
         }
      }
   }
   /**
    * 移动端升级,安装包下载
    *
    * @param fse
    * @param response
    */
   public void getFileContent(FieldSetEntity fse, HttpServletResponse response, boolean isUpgrade) throws IOException {
      if (isUpgrade) {
         FieldSetEntity attachmentFse = baseDao.getFieldSetEntity(CmnConst.PRODUCT_SYS_ATTACHMENTS, fse.getUUID(), false);
         if (attachmentFse == null || !"product_sys_app_version".equals(attachmentFse.getString("attachment_data_table"))) {
            return;
         }
         logger.info("正在获取文件流...");
         boolean needDownloadFromServerFlag = "1".equals(attachmentFse.getString(CmnConst.UPLOAD_SIGN));
         String dir = attachmentFse.getString(CmnConst.ATTACHMENT_URL);
         String fileName = attachmentFse.getString(CmnConst.ATTACHMENT_TITLE);
         //真实文件名
         String realFileName = attachmentFse.getString(CmnConst.FILE_NAME);
         String viewOnlineSign = attachmentFse.getString(CmnConst.VIEW_ONLINE_SIGN);
         boolean encrptSignFlag = "1".equals(attachmentFse.getString(CmnConst.ENCRPT_SIGN));
         boolean needOnlineViewFlag = "1".equals(fse.getString(CmnConst.NEED_ONLINE_VIEW)) && "1".equals(viewOnlineSign);
         String clientType = CoreConst.CLIENT_TYPE_APP;
         String contentType = "multipart/form-data";
         boolean isExcel = realFileName.endsWith(".xlsx") || realFileName.endsWith(".xls");
         if (needOnlineViewFlag) {
            //特殊处理: 如果客户端不是App 但预览的文件是Excel 直接返回excel源文件 content头标识是excel cheng 2025年2月11日10:41:41
            if (isExcel && !CoreConst.CLIENT_TYPE_APP.equals(clientType)) {
               contentType = "application/vnd.ms-excel";
            } else {
               dir += File.separator + CmnConst.TRANSFER_DIR_NAME;
            }
         }
         String path = dir + File.separator + fileName;
         if (needDownloadFromServerFlag) {
            // 需要从附件服务器上取文件
            FTPService ftpService = new FTPService();
            logger.info("需要从附件服务器上取文件...");
            response.setHeader("Access-Control-Expose-Headers", "*");
            response.setContentType(contentType);
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(realFileName, "UTF-8"));
            //设置头中的文件类型
            if (!needOnlineViewFlag && !encrptSignFlag) {
               logger.info("不需要解密且获取原文件...");
               try (ServletOutputStream os = response.getOutputStream()) {
                  ftpService.downloadFile(path, os);
               }
            } else {
               logger.info("需要解密或者获取转换后的文件...");
               String localTempPath = Global.getSystemConfig("temp.dir", "") + File.separator + fileName;
               File localTempFile = new File(localTempPath);
               OutputStream tempOs = new FileOutputStream(localTempFile);
               ftpService.downloadFile(path, tempOs);
               tempOs.flush();
               tempOs.close();
               if (needOnlineViewFlag && localTempFile.length() == 0L) {
                  // 若是预览,但是转换后的文件不存在,那么重新执行转换操作
                  File sourceFile = fileUtils.getFile(true, attachmentFse.getString(CmnConst.ATTACHMENT_URL), fileName, encrptSignFlag);
                  File tempSourceFile = new File(sourceFile.getParentFile().getAbsolutePath() + File.separator + attachmentFse.getString(CmnConst.FILE_NAME));
                  sourceFile.renameTo(tempSourceFile);
                  FileUtils.convertPdf(false, true, encrptSignFlag, true, tempSourceFile, dir, fileName, attachmentFse.getString(CmnConst.FILE_NAME));
                  tempSourceFile.delete();
                  // 重新拉文件
                  tempOs = new FileOutputStream(localTempFile);
                  ftpService = new FTPService();
                  ftpService.downloadFile(path, tempOs);
                  tempOs.flush();
                  tempOs.close();
               }
               response.setContentLengthLong(localTempFile.length());
               InputStream is = new FileInputStream(localTempFile);
               int len;
               byte[] b = new byte[1024];
               try (ServletOutputStream os = response.getOutputStream()) {
                  while ((len = is.read(b)) > 0) {
                     os.write(encrptSignFlag ? FileUtil.decryption(b) : b, 0, len);
                  }
                  os.flush();
                  is.close();
               }
               // 删除临时文件
               localTempFile.delete();
            }
         } else {
            // 直接在本地的目录中找文件
            logger.info("直接在本地的目录中找文件...");
            String localBasePath = Global.getSystemConfig("local.dir", "");
            path = localBasePath + File.separator + path;
            File file = new File(path);
            if (needOnlineViewFlag && file.exists() && CoreConst.CLIENT_TYPE_APP.equals(clientType)) {
               //特殊处理: 是App 但预览的文件是Excel 需要将之前已转换为的文件删除掉重新转换(因转换后的格式不是pdf)
               String changeTime = "2025-02-11 23:59:59";
               Date changeDate = DateUtil.parse(changeTime, "yyyy-MM-dd HH:mm:ss");
               long fileTime = file.lastModified();
               //如果时间是 2025年2月10日23:59:59,那么就重新转换
               //删除文件
               if (fileTime <= changeDate.getTime()) {
                  file.delete();
               }
            }
            if (needOnlineViewFlag && !file.exists()) {
               // 若是预览,但是转换后的文件不存在,那么重新执行转换操作
               File sourceFile = fileUtils.getFile(false, attachmentFse.getString(CmnConst.ATTACHMENT_URL), fileName, encrptSignFlag);
               File tempSourceFile = new File(sourceFile.getParentFile().getAbsolutePath() + File.separator + attachmentFse.getString(CmnConst.FILE_NAME));
               sourceFile.renameTo(tempSourceFile);
               FileUtils.convertPdf(false, false, encrptSignFlag, true, tempSourceFile, dir, fileName, attachmentFse.getString(CmnConst.FILE_NAME));
               tempSourceFile.delete();
            }
            int len;
            byte[] b = new byte[1024];
            InputStream is = new FileInputStream(file);
            response.setHeader("Access-Control-Expose-Headers", "*");
            response.setContentType(contentType);
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(realFileName, "UTF-8"));
            response.setContentLengthLong(file.length());
            try (ServletOutputStream os = response.getOutputStream()) {
               while ((len = is.read(b)) > 0) {
                  if (encrptSignFlag) {
                     // 需要解密
                     logger.info("需要解密...");
                     os.write(FileUtil.decryption(b), 0, len);
                  } else {
                     // 无需解密
                     os.write(b, 0, len);
                  }
               }
               os.flush();
            }
            is.close();
         }
         logger.info("文件流获取成功");
      }
   }
   /**
@@ -469,9 +773,17 @@
      String viewOnlineSign = attachmentFse.getString(CmnConst.VIEW_ONLINE_SIGN);
      boolean encrptSignFlag = "1".equals(attachmentFse.getString(CmnConst.ENCRPT_SIGN));
      boolean needOnlineViewFlag = "1".equals(fse.getString(CmnConst.NEED_ONLINE_VIEW)) && "1".equals(viewOnlineSign);
      if (needOnlineViewFlag && !realFileName.endsWith(".xlsx") && !realFileName.endsWith(".xls")) {
         // 需要在线预览且转换之后才能在线预览 excel 文件不需要转换直接输出预览
         dir += File.separator + CmnConst.TRANSFER_DIR_NAME;
      String clientType = SpringMVCContextHolder.getCurrentUser().getClientType();
      String contentType = "multipart/form-data";
      boolean isExcel = realFileName.endsWith(".xlsx") || realFileName.endsWith(".xls");
      if (needOnlineViewFlag) {
         //特殊处理: 如果客户端不是App 但预览的文件是Excel 直接返回excel源文件 content头标识是excel cheng 2025年2月11日10:41:41
         if (isExcel && !CoreConst.CLIENT_TYPE_APP.equals(clientType)) {
            contentType = "application/vnd.ms-excel";
         } else {
            dir += File.separator + CmnConst.TRANSFER_DIR_NAME;
         }
      }
      String path = dir + File.separator + fileName;
@@ -480,11 +792,8 @@
         FTPService ftpService = new FTPService();
         logger.info("需要从附件服务器上取文件...");
         response.setHeader("Access-Control-Expose-Headers", "*");
         if (realFileName.endsWith(".xlsx") || realFileName.endsWith(".xls")) {
            response.setContentType("application/vnd.ms-excel");
         } else {
            response.setContentType("multipart/form-data");
         }
         response.setContentType(contentType);
         response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(realFileName, "UTF-8"));
         //设置头中的文件类型
         if (!needOnlineViewFlag && !encrptSignFlag) {
@@ -515,6 +824,19 @@
               tempOs.flush();
               tempOs.close();
            }
            boolean delete = false;
            if (realFileName.endsWith(".xls") && needOnlineViewFlag && localTempFile.exists() && !CoreConst.CLIENT_TYPE_APP.equals(clientType)) {
               //转换为xlsx
               AsposeUtil.xls2xlsx(localTempFile.getPath(), getSystemConfig("temp.dir", "/attachment/temp") + "/" + localTempFile.getName(), "xls");
               File oldFile = localTempFile;
               localTempFile = new File(getSystemConfig("temp.dir", "/attachment/temp") + "/" + localTempFile.getName());
               if (!localTempFile.isFile()) {
                  localTempFile = oldFile;
               } else {
                  delete = true;
                  oldFile.delete();
               }
            }
            response.setContentLengthLong(localTempFile.length());
            InputStream is = new FileInputStream(localTempFile);
            int len;
@@ -530,32 +852,74 @@
            localTempFile.delete();
         }
      } else {
         // 直接在本地的目录中找文件
         logger.info("直接在本地的目录中找文件...");
         String localBasePath = Global.getSystemConfig("local.dir", "");
         path = localBasePath + File.separator + path;
         File file = new File(path);
         if (needOnlineViewFlag && !file.exists()) {
            // 若是预览,但是转换后的文件不存在,那么重新执行转换操作
            File sourceFile = fileUtils.getFile(false, attachmentFse.getString(CmnConst.ATTACHMENT_URL), fileName, encrptSignFlag);
            File tempSourceFile = new File(sourceFile.getParentFile().getAbsolutePath() + File.separator + attachmentFse.getString(CmnConst.FILE_NAME));
            sourceFile.renameTo(tempSourceFile);
            FileUtils.convertPdf(false, false, encrptSignFlag, true, tempSourceFile, dir, fileName, attachmentFse.getString(CmnConst.FILE_NAME));
            tempSourceFile.delete();
         }
            InputStream is = null;
            boolean delete = false;
            File file = null;
            long fileLength = 0L;
            Object fileContent = attachmentFse.getValue("file_content");
            // 是否存储到数据库
            boolean saveInDb = fileContent != null;
            if (saveInDb) {
                is = (InputStream) fileContent;
                fileLength = attachmentFse.getLong("attachment_size");
            }
            if (fileContent == null || needOnlineViewFlag) {
                // 直接在本地的目录中找文件
                logger.info("直接在本地的目录中找文件...");
                String localBasePath = Global.getSystemConfig("local.dir", "");
                path = localBasePath + File.separator + path;
                file = new File(path);
                if (needOnlineViewFlag && file.exists() && CoreConst.CLIENT_TYPE_APP.equals(clientType)) {
                    //特殊处理: 是App 但预览的文件是Excel 需要将之前已转换为的文件删除掉重新转换(因转换后的格式不是pdf)
                    String changeTime = "2025-02-11 23:59:59";
                    Date changeDate = DateUtil.parse(changeTime, "yyyy-MM-dd HH:mm:ss");
                    long fileTime = file.lastModified();
                    //如果时间是 2025年2月10日23:59:59,那么就重新转换
                    //删除文件
                    if (fileTime <= changeDate.getTime()) {
                        file.delete();
                    }
                }
                if (needOnlineViewFlag && !file.exists()) {
                    // 若是预览,但是转换后的文件不存在,那么重新执行转换操作
                    // 若是存储到数据库的,那么先查看本地是否存在,若是不存在,那么存一份到本地
                    if (saveInDb) {
                        File localFile = new File(Global.getSystemConfig("local.dir", "") + File.separator + attachmentFse.getString(CmnConst.ATTACHMENT_URL) + File.separator + fileName);
                        if (!localFile.exists()) {
                            Files.copy(is, localFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
                        }
                    }
                    File sourceFile = fileUtils.getFile(false, attachmentFse.getString(CmnConst.ATTACHMENT_URL), fileName, encrptSignFlag);
                    File tempSourceFile = new File(sourceFile.getParentFile().getAbsolutePath() + File.separator + attachmentFse.getString(CmnConst.FILE_NAME));
                    sourceFile.renameTo(tempSourceFile);
                    FileUtils.convertPdf(false, false, encrptSignFlag, true, tempSourceFile, dir, fileName, attachmentFse.getString(CmnConst.FILE_NAME));
                    tempSourceFile.delete();
                }
                if (realFileName.endsWith(".xls") && needOnlineViewFlag && file.exists() && !CoreConst.CLIENT_TYPE_APP.equals(clientType)) {
                    //转换为xlsx
                    AsposeUtil.xls2xlsx(file.getPath(), getSystemConfig("temp.dir", "/attachment/temp") + "/" + file.getName(), "xls");
                    File oldFile = file;
                    file = new File(getSystemConfig("temp.dir", "/attachment/temp") + "/" + file.getName());
                    if (!file.isFile()) {
                        file = oldFile;
                    } else {
                        delete = true;
                    }
                }
                is = Files.newInputStream(file.toPath());
                fileLength = file.length();
            }
         int len;
         byte[] b = new byte[1024];
         InputStream is = new FileInputStream(file);
         response.setHeader("Access-Control-Expose-Headers", "*");
         if (realFileName.endsWith(".xlsx") || realFileName.endsWith(".xls")) {
            response.setContentType("application/vnd.ms-excel");
         } else {
            response.setContentType("multipart/form-data");
         }
            response.setContentType("application/octet-stream");
         response.setContentType(contentType);
         response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(realFileName, "UTF-8"));
         response.setContentLengthLong(file.length());
         try (ServletOutputStream os = response.getOutputStream()) {
            while ((len = is.read(b)) > 0) {
         response.setContentLengthLong(fileLength);
            try (ServletOutputStream os = response.getOutputStream()) {
            while ((len = is.read(b)) != -1) {
               if (encrptSignFlag) {
                  // 需要解密
                  logger.info("需要解密...");
@@ -568,6 +932,9 @@
            os.flush();
         }
         is.close();
         if (delete && file != null) {
            file.delete();
         }
      }
      logger.info("文件流获取成功");
   }