package com.rongwei.bscommon.sys.service.impl;

import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.Header;
import cn.hutool.http.HttpRequest;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import com.alibaba.fastjson.JSONObject;
import com.aspose.pdf.*;
import com.aspose.pdf.HorizontalAlignment;
import com.aspose.pdf.VerticalAlignment;
import com.aspose.words.Document;
import com.aspose.words.SaveFormat;
import com.aspose.words.TextWatermarkOptions;
import com.aspose.words.WatermarkLayout;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bsentity.domain.*;
import com.bsentity.dto.*;
import com.bsentity.enums.ErrorCodeEnum;
import com.bsentity.vo.CloseProcVo;
import com.bsentity.vo.DealWithFileVO;
import com.bsentity.dto.InportErrorFileVo;
import com.bsentity.vo.FsFileFormVo;
import com.rongwei.bscommon.sys.dao.FsFileDataListDao;
import com.rongwei.bscommon.sys.fegin.CommonFeginClient;
import com.rongwei.bscommon.sys.fegin.LoginAuth;
import com.rongwei.bscommon.sys.service.*;
import com.rongwei.bscommon.sys.utils.FileSystemUtils;
import com.rongwei.rwadmincommon.system.domain.SysUserDo;
import com.rongwei.rwadmincommon.system.service.SysUserService;
import com.rongwei.rwcommon.base.R;
import com.rongwei.rwcommon.utils.SecurityUtil;
import com.rongwei.rwcommonentity.commonservers.domain.SysFileItemDo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.lang.StringUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.Color;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.DecimalFormat;
import java.util.List;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * <p>
 * 奥克宁可——文件清单 服务实现类
 * </p>
 *
 * @author trj
 * @since 2023-07-17
 */
@Slf4j
@Service
public class FsFileDataListServiceImpl extends ServiceImpl<FsFileDataListDao, FsFileDataListDo> implements FsFileDataListService {

    @Autowired
    private FsFileDataListDao fileDataListDao;
    @Autowired
    private FsFileDataListRecordService fsFileDataListRecordService;
    @Autowired
    private FsTrainingStaffRecordService fsTrainingStaffRecordService;
    @Autowired
    private FsTrainingStaffService fsTrainingStaffService;
    @Autowired
    private CommonFeginClient commonFeginClient;
    @Autowired
    private FsFileDataListDao fsFileDataListDao;
    @Autowired
    private FsFileNavigationService fsFileNavigationService;
    @Autowired
    private SysUserService sysUserService;
    @Autowired
    private LoginAuth loginAuth;

    /**
     * 水印处理逻辑
     * 背景： 根据如下流程操作
     * 1、新文件受控；  -- > 结果： 生成水印。 （水印内容 ： “受控” +  审核时间）
     * 2、文件更新； -->  结果： 给 【1】 产生的文件 变更水印 （受控 --> 失效）； 给【2】产生的文件 打上 “受控“ 水印
     * 3、文件更新； -->  结果： 给 【3】 产生的文件 打上 ”受控“ 水印； 给 【2】 产生的文件 变更水印 （受控 --> 失效）
     * 4、文件作废； -->  结果： 给 【3】 产生的文件 变更水印 （失效 --> 作废）；（注意： 文件作废时， 只会传回  历史文件Id）
     * <p>
     * <p>
     * 实现方式：
     * 1. 通过 Vo对象中的 id + historyId 去 sys_file_item  查询文件 绝对路径
     * <p>
     * 2. 根据 Vo对象中的 type 做新增 || 变更 || 作废 操作
     * <p>
     * 3. 水印处理完成，保存文档
     *
     * @param dealWithFileVO 表单ID信息
     * @return
     */
    @Override
    public R watermark(DealWithFileVO dealWithFileVO) {
        //查询附件id
        List<FileIdDto> fileIdDtos = baseMapper.getFileIds(dealWithFileVO);

        if (ArrayUtil.isEmpty(fileIdDtos) && fileIdDtos.size() == 0) {
            return R.error("未获取到文件");
        }

        // 获取文件路径所属表单id
        List<FileDto> fileDtos = getFilePath(fileIdDtos, dealWithFileVO.getType());
        log.info("文件路径所属表单id ---> {}", fileDtos);
        if (fileDtos.isEmpty()) {
            return R.ok("附件为空");
        }

        // 分组处理
        Map<String, List<FileDto>> fileTypeMap = fileDtos.stream().collect(Collectors.groupingBy(FileDto::getFileType));

        //根据 前端传输的操作进行 打水印
        try {
            fileTypeMap.forEach((type, fileList) -> {
                try {
//                    if ("docx".equals(type) || "doc".equals(type)) {
//                        dealWithWordWatermarking(fileList, dealWithFileVO.getType());
//                    } else if("pdf".equals(type)){
//                        dealWithPDFWatermarking(fileList, dealWithFileVO.getType());
//                    }
                    if ("pdf".equals(type)) {
                        dealWithPDFWatermarking(fileList, dealWithFileVO.getType(), "manual");
                    }
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }

            });
            return R.ok();
        } catch (Exception e) {
            e.printStackTrace();
            log.error(" 接口请求打水印报错 ----> {}", e.getMessage());
            return R.error(e.getMessage());
        }

    }

    /**
     * 定时任务，所有生效文件进行回顾
     *
     * @return
     */
    @Override
    public R updateReviewByAll() {
        //清空当前所有文件状态
        LambdaUpdateWrapper<FsFileDataListDo> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
        lambdaUpdateWrapper.set(FsFileDataListDo::getReview, null);
        fileDataListDao.update(null, lambdaUpdateWrapper);

        LambdaQueryWrapper<FsFileDataListDo> queryWrapper = new LambdaQueryWrapper<>();
        //是否删除为否
        queryWrapper.eq(FsFileDataListDo::getDeleted, 0)
                //是否作废为否
                .eq(FsFileDataListDo::getCancellation, 0)
                //文件状态为生效中
                .eq(FsFileDataListDo::getFilestate, 1)
                //流程状态不为50的
                .ne(FsFileDataListDo::getProcessinststatus, 50)
                .select(FsFileDataListDo::getId, FsFileDataListDo::getCreateuserid);
        //当前时间小于过期时间
        queryWrapper.and(QueryWrapper -> QueryWrapper.gt(FsFileDataListDo::getExpirationtime, new Date())
                //或者过期时间为空
                .or().isNull(FsFileDataListDo::getExpirationtime));
        List<FsFileDataListDo> fileDataListList = fileDataListDao.selectList(queryWrapper);
        //获取时间
        Calendar calendar = Calendar.getInstance();
        int year = calendar.get(Calendar.YEAR);
        LambdaUpdateWrapper<FsFileDataListDo> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.in(FsFileDataListDo::getId, fileDataListList.stream().map(FsFileDataListDo::getId).collect(Collectors.toList()))
                //修改回顾状态为1
                .set(FsFileDataListDo::getReview, 1);
        fileDataListDao.update(null, updateWrapper);
        //发送预警
        FileSystemUtils.sendNotify(
                "年度文件回顾提醒",
                year + "年年度回顾请及时处理",
                year + "-" + calendar.get(Calendar.MONTH) + "-" + calendar.get(Calendar.DAY_OF_MONTH),
                fileDataListList.stream().map(FsFileDataListDo::getCreateuserid).distinct().collect(Collectors.toList())
        );
        return R.ok(ErrorCodeEnum.SUCCESS);
    }

    @Override
    public void earlyWarningReminder(RemindDto remindDto) {
        FileSystemUtils.sendNotify(
                remindDto.getTitle(),
                remindDto.getContent(),
                remindDto.getRemark(),
                remindDto.getRecipientIds());


    }

    /**
     * 每天8点执行一次，
     * 1.查询过期的临时文件。
     * 2.打上 失效 标签
     *
     * @return BaseCode
     */
    @Override
    public synchronized R temporaryTask() {
        List<FileIdDto> fileIdDtos = baseMapper.getTemporaryFilePath();

        if (ArrayUtil.isEmpty(fileIdDtos) && fileIdDtos.size() == 0) {
            return R.error("未获取到文件");
        }

        // 获取文件路径所属表单id
        List<FileDto> fileDtos = getFilePath(fileIdDtos, 2);
        log.info("文件路径所属表单id ---> {}", fileDtos);
        if (fileDtos.isEmpty() && fileIdDtos == null) {
            return R.ok("未查到附件。");
        }

        // 分组处理
        Map<String, List<FileDto>> fileTypeMap = fileDtos.stream().collect(Collectors.groupingBy(FileDto::getFileType));

        log.info(" 定时任务请求打水印 ----> {}", fileTypeMap);
        //根据 前端传输的操作进行 打水印
        try {
            fileTypeMap.forEach((type, fileList) -> {
                try {
//                    if ("docx".equals(type) || "doc".equals(type)) {
//                        dealWithWordWatermarking(fileList, 3);
//                        log.info(" ---> word 处理完成");
//
//                    } else if  {
//                        dealWithPDFWatermarking(fileList, 2);
//                        log.info(" ---> pdf 处理完成");
//                    }
                    if ("pdf".equals(type)) {
                        dealWithPDFWatermarking(fileList, 2, "auto");
                        log.info(" ---> pdf 处理完成");
                    }
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }

            });
            System.gc();
            return R.ok();
        } catch (Exception e) {
            e.printStackTrace();
            log.error(" 定时任务请求打水印报错 ----> {}", e.getMessage());
            return R.error(e.getMessage());
        }


    }


    /**
     * 变更流程关闭流程
     * 1.  （前端传输ID 查找数据）当前数据加入至  历史表
     * <p>
     * 2.  删除 发放人员数据。
     * <p>
     * 3.  在 历史表中找到最新的历史记录 存入  主表。
     * <p>
     * 4. 将 3 中 的插入的主表ID ，所在的 发放人员 存入 发放人员数据
     * <p>
     * 如果是作废，取消流程。 版本号不变。其他照旧
     *
     * @param closeProcVo 主表id， 流程标识
     * @return
     */
    @Override
    public R closeProc(CloseProcVo closeProcVo) {
        LambdaQueryWrapper<FsFileDataListDo> fsFDLambdaQueryWrapper = new LambdaQueryWrapper<>();
        fsFDLambdaQueryWrapper.eq(FsFileDataListDo::getId, closeProcVo.getId());
        // 关闭数据 -- > 需要存入历史表中
        FsFileDataListDo fsFileDataListDo = baseMapper.selectOne(fsFDLambdaQueryWrapper);
        log.info("关闭流程主表信息 ：{}", fsFileDataListDo);
        if (ObjectUtil.isEmpty(fsFileDataListDo)) {
            return R.error("当前流程信息为空");
        }

        // 流程版本信息
        String serialnumber = fsFileDataListDo.getSerialnumber();
        if (closeProcVo.getIsDel() == null || Objects.equals(closeProcVo.getIsDel(), "")) {
            Integer integer = Integer.valueOf(serialnumber);
            integer = integer - 1;

            if (integer > 0 && integer > 10) {
                serialnumber = integer.toString();
            } else {
                serialnumber = "0" + integer;
            }
        }


        // 通过 id  再去历史表中查询上一版本,已完成的数据
        // 需要存入主表
        FsFileDataListRecordDo fsFileDataListRecordDo = fsFileDataListRecordService.getBaseMapper()
                .selectOne(new LambdaQueryWrapper<FsFileDataListRecordDo>()
                        .eq(FsFileDataListRecordDo::getMasterid, closeProcVo.getId())
                        .eq(FsFileDataListRecordDo::getSerialnumber, serialnumber)
                        .eq(FsFileDataListRecordDo::getProcessinststatus, "20")
                        .last("limit 1"));
        log.info("上一版本主表信息 ：{}", fsFileDataListRecordDo);
        if (ObjectUtil.isEmpty(fsFileDataListRecordDo)) {
            return R.error("未读到上一版本信息， 请检查。");
        }

        // 查询 历史记录的 发放人员
        List<FsTrainingStaffRecordDo> fsTrainingStaffRecordDos = fsTrainingStaffRecordService.getBaseMapper()
                .selectList(new LambdaQueryWrapper<FsTrainingStaffRecordDo>()
                        .eq(FsTrainingStaffRecordDo::getMasterid, fsFileDataListRecordDo.getId()));
        log.info("上一版本培训人员 ： {}", fsTrainingStaffRecordDos);

        // 删除主表中的培训发放人员
        int delete = fsTrainingStaffService.getBaseMapper()
                .delete(new LambdaQueryWrapper<FsTrainingStaffDo>()
                        .eq(FsTrainingStaffDo::getMasterid, fsFileDataListDo.getId()));


        //数据插入
        return saveFileData(fsFileDataListDo, fsFileDataListRecordDo, fsTrainingStaffRecordDos);
    }

    /**
     * 初始化知识库数据。
     * 1. 前端传输文件夹路径。
     * <p>
     * 2. 程序扫描文件夹，读取文件类型。
     * 2.1 如果是 pdf文件 ， 打上 ”受控水印“。
     * 2.2 执行上传接口 。 并且 sys_file_item 扩展字段2 存储 文件编号。
     * 2.3 存储Map<文件编号 ， ID> 结构的数据
     * <p>
     * 3. 读取Excel文件， 将数据存入fs_file_data_list.
     * 3.1 先根据 树状文件目录，去读取文件树ID。
     * 3.2 读取 字典信息：
     * a）、文件层次 、部门 、前缀 （树状结构） 按对应字典值存入数据库。
     * 3.3 读取文件Id,并赋值
     * 3.4 存入数据库 。
     *
     * @param folderPath 文件夹
     * @return
     */
    @Override
    public R initialization(String folderPath) {
        if (StrUtil.isEmpty(folderPath)) {
            return null;
        }

        //扫描了文件夹
        try (Stream<Path> paths = Files.walk(Paths.get(folderPath))) {
            // 附件路径
            List<Path> filePath = paths.filter(Files::isRegularFile).collect(Collectors.toList());
            log.info("附件路径名称:{}", filePath);

            //  根据文件扩展名分组
            List<Path> pdfFilePath = new ArrayList<>();
            List<Path> cellFilePath = new ArrayList<>();

            for (Path path : filePath) {
                if (path.getFileName().toString().substring(path.getFileName().toString().lastIndexOf(".") + 1).equals("pdf")) {
                    pdfFilePath.add(path);
                }
                if (path.getFileName().toString().substring(path.getFileName().toString().lastIndexOf(".") + 1).equals("xlsx")) {
                    cellFilePath.add(path);
                }
            }

            // pdf文件处理
            List<SysFileItemDo> sysFileItemDos = pdfFileTodo(pdfFilePath);

            if (ArrayUtil.isEmpty(sysFileItemDos)) {
                return R.error();
            }

            // Excel文件数据导入
            importExcel(cellFilePath, sysFileItemDos);

            return R.ok();
        } catch (Exception e) {
            e.printStackTrace();
            return R.error(e.getMessage());
        }

    }


    /**
     * 奥克宁可文件初始化定制版
     * <p>
     * 代码思路：
     * 前端 ：  前端给入根目录 【文件地址】。
     * 后端：
     * 1. 扫描文件夹地址。 是否存在.pdf文件。
     * （1）、 读取.pdf 文件 ，去前4位 比对前缀。 如果有，则 获取 前两级 数据字典（部门、字典）
     * （2）、 执行 pdf 水印处理 ， 执行上传操作 。
     * （3）、 分析pdf文件名。 生成 List<FsFileDataListDo> 数据，文件路径生成对应的文件树
     * （4）、 将当前文件夹路径作为参数递归调用
     * 2.  其次当前文件夹下是否还有新的文件夹数据。 没有，取上一级文件夹地址作为参数
     * 2. 如果没有， 则解析下面的文件夹。直至碰到.pdf的文件夹。
     *
     * @param folderPath
     * @return
     */
    @Override
    public void aknkInitFolder(String folderPath, HttpServletResponse response) {
        if (StrUtil.isEmpty(folderPath)) {
            log.error("请输入初始化文件路径");
            return;
        }

        Map<String, List<File>> filePathMap = initFile(folderPath, new HashMap<String, List<File>>());
        log.info(" 文件路径Map -> {}", filePathMap);


        //  处理数据
        makeFile(filePathMap, response);


    }

    @Override
    public R reSaveFileRecord(FsFileFormVo fsFileFormVo) {
        if (ObjectUtil.isEmpty(fsFileFormVo)) {
            return null;
        }
        // 查询历史记录最大版本
        Wrapper<FsFileDataListRecordDo> wrapper = new LambdaQueryWrapper<FsFileDataListRecordDo>()
                .eq(FsFileDataListRecordDo::getMasterid, fsFileFormVo.getMainId())
                .eq(FsFileDataListRecordDo::getProcessinststatus, "20")
                .orderByDesc(FsFileDataListRecordDo::getSerialnumber)
                .last("LIMIT 1");
        // 历史记录最大版本
        FsFileDataListRecordDo fsFileDataListRecordDo = fsFileDataListRecordService.getBaseMapper().selectOne(wrapper);
        BeanUtil.copyProperties(fsFileFormVo, fsFileDataListRecordDo);
        fsFileDataListRecordDo.setDepartment(fsFileFormVo.getDepartment() == null ? null : fsFileFormVo.getDepartment());
        fsFileDataListRecordDo.setCategory(fsFileFormVo.getCategory() == null ? null : fsFileFormVo.getCategory());
        fsFileDataListRecordDo.setAuditor(fsFileFormVo.getAuditor() == null ? null : fsFileFormVo.getAuditor());
        fsFileDataListRecordDo.setAuditorid(fsFileFormVo.getAuditorid() == null ? null : fsFileFormVo.getAuditorid());
        log.info("赋值后，历史记录 -> {}", fsFileDataListRecordDo);
        fsFileDataListRecordService.saveOrUpdate(fsFileDataListRecordDo);
        return R.ok("200", "更新成功", fsFileDataListRecordDo.getId());
    }


    /**
     * 1.给PDF打水印
     * 2.上传附件
     * 3.清洗数据， 存入数据库
     *
     * @param filePathMap
     */
    private void makeFile(Map<String, List<File>> filePathMap, HttpServletResponse response) {

        List<InportErrorFileVo> exportList = new ArrayList<>();

        Map<String, List<SysFileItemDo>> listMap = new HashMap<>();
        // 水印处理 + 附件上传
        filePathMap.forEach((path, files) -> {
            // 上传附件的id
            FileItemDto fileItemDto = initWaterMarkAKNK(files);
            log.info("文件上传  -> {}", fileItemDto.getFileItemDoList());
            listMap.put(path, fileItemDto.getFileItemDoList());
            exportList.addAll(fileItemDto.getInportErrorFileVos());
        });


        // 数据清洗
        dataToBase(listMap, exportList, response);
    }


    /**
     * 1. Map 的key 进行分解， 存入数据库中。
     * 2. 组装 fs_file_data_list 数据。
     * 3. 生成失败报告。
     *
     * @param listMap    成功上传的文件， 生成文件数据
     * @param exportList 未成功上传文件数据 ， 导出失败报告
     * @param response   报告二进制文件
     */
    @Transactional(rollbackFor = Exception.class)
    public void dataToBase(Map<String, List<SysFileItemDo>> listMap, List<InportErrorFileVo> exportList, HttpServletResponse response) {
        List<FsFileDataListDo> fsFileDataListDos2 = new ArrayList<>();
        List<FsFileNavigationDo> fsFileNavigationDos = new ArrayList<>();
        // 查询前缀字典数据
        List<FileFloorDto> dict = fsFileDataListDao.getDict();
        List<FileFloorDto> ones = new ArrayList<>();
        List<FileFloorDto> twos = new ArrayList<>();

        for (FileFloorDto fileFloorDto : dict) {
            if (fileFloorDto.getName() == null) {
                ones.add(fileFloorDto);
            } else {
                twos.add(fileFloorDto);
            }
        }

        Map<String, List<FileFloorDto>> fMap = Optional.of(ones).orElse(new ArrayList<>()).stream().collect(Collectors.groupingBy(FileFloorDto::getFName));
        Map<String, List<FileFloorDto>> dictMap = Optional.of(twos).orElse(new ArrayList<>()).stream().collect(Collectors.groupingBy(FileFloorDto::getPName));
        dictMap.putAll(fMap);
        log.info("前缀字典 -> {}", dictMap);

        AtomicReference<List<FileFloorDto>> fileFloorDtos = new AtomicReference<>();

        listMap.forEach((path, fileItemDos) -> {
            log.info("处理路径 -> {}", path);
            // 解析路径，生成文件树数据。
            FNDto fnDto = gFileTree(path, fsFileNavigationDos);

            fsFileNavigationDos.addAll(fnDto.getFnDos());

            log.info("文件树信息 -> {}", fnDto.getFoldName());

            // 拼接DictMap key
            String pathName = "";
            if (fnDto.getFoldName().size() == 1) {
                pathName = fnDto.getFoldName().get(0);
            } else {
                pathName = fnDto.getFoldName().get(1);
            }
            if (pathName.contains("(PFMEA&CP)")) {
                fileFloorDtos.set(dictMap.get("二层控制计划(PFMEA&CP)"));
            } else if (pathName.contains("一层次") || pathName.contains("二层次")) {
                fileFloorDtos.set(dictMap.get("一层手册/二层程序文件"));
            } else if (pathName.contains("三层次")) {
                // 部门
                String fnDo3 = fnDto.getFoldName().get(2);
                if (fnDo3.contains("技术")) {
                    fileFloorDtos.set(dictMap.get("三层次文件\\技术"));
                } else if (fnDo3.contains("质量")) {
                    fileFloorDtos.set(dictMap.get("三层次文件\\质量"));
                } else if (fnDo3.contains("计划")) {
                    fileFloorDtos.set(dictMap.get("三层次文件\\计划"));
                } else if (fnDo3.contains("采购")) {
                    fileFloorDtos.set(dictMap.get("三层次文件\\采购"));
                } else if (fnDo3.contains("生产")) {
                    fileFloorDtos.set(dictMap.get("三层次文件\\生产"));
                } else if (fnDo3.contains("市场")) {
                    fileFloorDtos.set(dictMap.get("三层次文件\\市场"));
                } else if (fnDo3.contains("IT")) {
                    fileFloorDtos.set(dictMap.get("三层次文件\\IT"));
                } else if (fnDo3.contains("人")) {
                    fileFloorDtos.set(dictMap.get("三层次文件\\人事"));
                } else if (fnDo3.contains("财务")) {
                    fileFloorDtos.set(dictMap.get("三层次文件\\财务"));
                } else if (fnDo3.contains("维修工程")) {
                    fileFloorDtos.set(dictMap.get("三层次文件\\维修工程"));
                } else if (fnDo3.contains("ABS")) {
                    fileFloorDtos.set(dictMap.get("三层次文件\\ABS"));
                }
            } else if (pathName.contains("四层次")) {
                // 部门
                String fnDo3 = fnDto.getFoldName().get(2);
                if (fnDo3.contains("技术质量")) {
                    fileFloorDtos.set(dictMap.get("四层次文件\\技术质量"));
                } else if (fnDo3.contains("计划")) {
                    fileFloorDtos.set(dictMap.get("四层次文件\\计划"));
                } else if (fnDo3.contains("采购")) {
                    fileFloorDtos.set(dictMap.get("四层次文件\\采购"));
                } else if (fnDo3.contains("生产")) {
                    fileFloorDtos.set(dictMap.get("四层次文件\\生产"));
                } else if (fnDo3.contains("人")) {
                    fileFloorDtos.set(dictMap.get("四层次文件\\人事"));
                } else if (fnDo3.contains("财务")) {
                    fileFloorDtos.set(dictMap.get("四层次文件\\财务"));
                } else if (fnDo3.contains("安全")) {
                    fileFloorDtos.set(dictMap.get("四层次文件\\安全"));
                } else if (fnDo3.contains("维修工程")) {
                    fileFloorDtos.set(dictMap.get("四层次文件\\维修工程"));
                } else if (fnDo3.contains("ABS")) {
                    fileFloorDtos.set(dictMap.get("三层次文件\\ABS"));
                }
            }


            // 解析文件名，生成业务数据
            List<FsFileDataListDo> fsFileDataListDos = gFileDateList(fnDto, fileItemDos, fileFloorDtos);
            if (!fsFileDataListDos.isEmpty()) {
                fsFileDataListDos2.addAll(fsFileDataListDos);
            }

        });
        // 生成历史数据
        gHistoryFDList(fsFileDataListDos2);
        // 数据持久化
        saveOrUpdateBatch(fsFileDataListDos2);
        fsFileNavigationService.saveOrUpdateBatch(fsFileNavigationDos);

        log.info("初始化完成");

        // 失败报告生成
        exportErrorFile(exportList, response);
    }

    /**
     * 历史数据生成
     *
     * @param fsFileDataListDos2
     */
    private void gHistoryFDList(List<FsFileDataListDo> fsFileDataListDos2) {
        List<FsFileDataListRecordDo> fsFileDataListRecordDos = new ArrayList<>();

        for (FsFileDataListDo fileDataListDo : fsFileDataListDos2) {
            FsFileDataListRecordDo fsFileDataListRecordDo = BeanUtil.copyProperties(fileDataListDo, FsFileDataListRecordDo.class);

            fsFileDataListRecordDo.setId(SecurityUtil.getUUID());
            fsFileDataListRecordDo.setMasterid(fileDataListDo.getId());
            fsFileDataListRecordDos.add(fsFileDataListRecordDo);
        }

        fsFileDataListRecordService.saveOrUpdateBatch(fsFileDataListRecordDos);
    }

    /**
     * 解析文件名，生成业务数据
     *
     * @param fnDto         文件树数据
     * @param fileItemDos   文件信息
     * @param fileFloorDtos 前缀字典
     * @return
     */
    private List<FsFileDataListDo> gFileDateList(FNDto fnDto, List<SysFileItemDo> fileItemDos, AtomicReference<List<FileFloorDto>> fileFloorDtos) {
        log.info(" --- > 生成业务数据");
        List<FsFileDataListDo> fsFileDataListDos = new ArrayList<>();
        for (SysFileItemDo fileItemDo : fileItemDos) {
            if (ObjectUtil.isEmpty(fileItemDo)) {

                continue;
            }
            FsFileDataListDo fileDataDo = new FsFileDataListDo();
            fileDataDo.setId(SecurityUtil.getUUID());
            fileDataDo.setCreateusername("auto");
            fileDataDo.setCreatedate(new Date());
            fileDataDo.setModifydate(new Date());
            fileDataDo.setDeleted("0");
            // 前缀匹配
            matchPrefix(fileDataDo, fileItemDo, fileFloorDtos);
            // 判断是前缀还是能源还是EHS
            if ("quality_system".equals(fnDto.getFoldName().get(0))) {
                if (fnDto.getFoldName().contains("EHS")) {
                    fileDataDo.setIsehs("1");
                } else {
                    fileDataDo.setIsehs("3");
                }
            } else if ("EHS".equals(fnDto.getFoldName().get(0))) {
                fileDataDo.setIsehs("1");
            } else {
                fileDataDo.setIsehs("2");
            }
            fileDataDo.setDocumenttreeid(fnDto.getNodeId());
            fileDataDo.setProcessinststatus("20");
            fileDataDo.setTemporaryfile("1");
            fsFileDataListDos.add(fileDataDo);
        }

        // 匹配前缀并生成业务数据
        return fsFileDataListDos;

    }


    /**
     * 匹配前缀并生成业务数据 - 思路
     * 1. 字典前缀是根据 文件层次 - 部门 - 前缀 。  与 文件存放的路径一致。
     * 2. 通过解析文件路径（文件层次 - 部门 ） 去找 复合条件的前缀。
     * 3. 根据条件位数分组。 然后通过位数去匹配。 （KMP算法）
     * 4. 匹配完前缀后，切割文件名，生成业务数据。
     * 5. 文件名规则： 前缀+序号+文件名+文件版本号
     *
     * @param fsFileDataListDo 业务数据
     * @param fileItemDos      附件信息
     * @param ffDto            字典信息
     * @return 业务数据
     */
    private void matchPrefix(FsFileDataListDo fsFileDataListDo, SysFileItemDo fileItemDos, AtomicReference<List<FileFloorDto>> ffDto) {
        log.info(" ---> 前缀匹配.....");
        log.info("业务数据:  ->   {} ", fsFileDataListDo);
        log.info("附件信息:  ->   {} ", fileItemDos);
        log.info("字典信息:  ->   {} ", ffDto);
        if (ffDto.get() != null) {
            log.info("开始匹配前缀：--> {}", ffDto);
            // 不为空， 匹配前缀
            List<FileFloorDto> fileFloorDtos = ffDto.get();
            if (!fileFloorDtos.isEmpty()) {
                for (FileFloorDto fileFloorDto : fileFloorDtos) {
                    if (StrUtil.isNotEmpty(fileFloorDto.getName())) {
                        if (fileItemDos.getFilename().contains(fileFloorDto.getName())) {
                            // 前缀匹配。切割文件名。生成数据
                            String[] split = fileItemDos.getFilename().split(fileFloorDto.getName());
                            getPrefix(fsFileDataListDo, fileFloorDto, split);
                        }
                    } else if (StrUtil.isNotEmpty(fileFloorDto.getPName())) {
                        if (fileItemDos.getFilename().contains(fileFloorDto.getPName())) {
                            // 前缀匹配。切割文件名。生成数据
                            String[] split = fileItemDos.getFilename().split(fileFloorDto.getPName());
                            getPrefix(fsFileDataListDo, fileFloorDto, split);
                        }

                    } else {
                        fsFileDataListDo.setSerialnumber("00");
                    }

                }
            }
        } else {
            fsFileDataListDo.setSerialnumber("00");
        }
        fsFileDataListDo.setFilename(fileItemDos.getFilename().substring(0, fileItemDos.getFilename().lastIndexOf(".")));
        fsFileDataListDo.setSpecification("文件导入");
        fsFileDataListDo.setCurrentstate("1");
        if ("pdf".equals(fileItemDos.getFiletype())) {
            fsFileDataListDo.setFilepdf(fileItemDos.getFilename() + "-;-" + fileItemDos.getId());
            fsFileDataListDo.setFiletCateGory("1");
        } else {
            fsFileDataListDo.setFileword(fileItemDos.getFilename() + "-;-" + fileItemDos.getId());
            fsFileDataListDo.setFiletCateGory("2");
        }
    }

    private void getPrefix(FsFileDataListDo fsFileDataListDo, FileFloorDto fileFloorDto, String[] split) {
        String s = split[1];

        s = s.split("\\.")[0];
        int length = s.length();

        String num = s.substring(0, 2);
        String verNum = s.substring(length - 2, length);
        String name = s.substring(2, length - 2);


        fsFileDataListDo.setFilename(name);
        fsFileDataListDo.setSerialnumber(verNum);
        fsFileDataListDo.setFileencoding(fileFloorDto.getName() + num);
        fsFileDataListDo.setVariety(fileFloorDto.getFValue());
        fsFileDataListDo.setDepartment(fileFloorDto.getPValue());
        fsFileDataListDo.setCategory(fileFloorDto.getValue());
    }

    /**
     * 解析文件树
     *
     * @param path
     * @param fsFileNavigationDos
     * @return
     */
    private FNDto gFileTree(String path, List<FsFileNavigationDo> fsFileNavigationDos) {

        List<String> paths = Arrays.asList(path.split("\\\\"));
        List<FsFileNavigationDo> fs = new ArrayList<>();
        String id = "";
        int index = 0;
        boolean flag = false; // 结尾标志位
        for (String p : paths) {
            if (!p.contains(":")) {
                // 父级文件树路径
                StringBuilder pa = new StringBuilder();

                for (int i = 1; i <= index; i++) {
                    if (index == 1) {
                        pa.append(p);
                    } else if (i == index) {
                        pa.append(paths.get(i));
                    } else {
                        pa.append(paths.get(i)).append("\\");
                    }
                }


                String s = paths.get(index - 1).contains(":") ? p : paths.get(index - 1);
                if (paths.size() - 1 == index) {
                    flag = true;
                }
                // 利用递归生成树
                FsFileNavigationDo fsFileNavigationDo = getNavigation(pa.toString(), fsFileNavigationDos, fs, s, p, flag);
                if (ObjectUtil.isEmpty(fsFileNavigationDo)) {
                    ++index;
                    continue;
                }

                fsFileNavigationDo.setId(SecurityUtil.getUUID());
                fsFileNavigationDo.setLevel(index);
                fsFileNavigationDo.setDeleted("0");
                fsFileNavigationDo.setCreatedate(new Date());
                fsFileNavigationDo.setModifydate(new Date());
                fsFileNavigationDo.setNodename(p);
                fsFileNavigationDo.setSort(1);

                if (paths.size() - 1 == index) {
                    id = fsFileNavigationDo.getId();
                }
                if (StrUtil.isEmpty(fsFileNavigationDo.getRoption())) {
                    fs.add(fsFileNavigationDo);
                }

            }

            ++index;
        }
        List<String> collect = paths.stream().filter(p -> !p.contains(":")).collect(Collectors.toList());
        return new FNDto(fs, id, collect);
    }

    /**
     * 解析路径生成文件树数据
     * 思路：
     * 1、 判断路径节点是否生成， 生成则跳过，进入下一节点数据。未生成则进行数据处理
     * 2、 将 fs & okFs 按照 remark 进行分组 ，string 去 okFsMap 获取数据。
     * 3、 获取不到，则去 fsMap 获取数据。 如果还是为空 去上一节点名称去获取。继续为空 即为 根节点
     * 4、 能获取 到节点，将id 作为当前节点 pid。
     *
     * @param string   路径信息
     * @param okFs     已存在文件树数据
     * @param fs       当前路径各数据
     * @param parent   上一节点名称
     * @param nodeName 当前节点名称
     * @param flag     结尾标志位
     * @return 当前节点文件树数据
     */
    private FsFileNavigationDo getNavigation(String string, List<FsFileNavigationDo> okFs, List<FsFileNavigationDo> fs,
                                             String parent, String nodeName, boolean flag) {

        FsFileNavigationDo fsFileNavigationDo = new FsFileNavigationDo();


        Map<String, List<FsFileNavigationDo>> okFsMap = Optional.of(okFs).orElse(new ArrayList<>())
                .stream()
                .collect(Collectors.groupingBy(FsFileNavigationDo::getRemark));

        Map<String, List<FsFileNavigationDo>> fsMap = Optional.of(fs).orElse(new ArrayList<>())
                .stream()
                .collect(Collectors.groupingBy(FsFileNavigationDo::getRemark));


        if (okFsMap.isEmpty() && fsMap.isEmpty()) {
            fsFileNavigationDo.setPid("-1");
            fsFileNavigationDo.setRemark(string);
        } else {
            //
            OK_MAP:
            if (!okFsMap.isEmpty()) {
                List<FsFileNavigationDo> fsFileNavigationDos = okFsMap.get(string);
                if (fsFileNavigationDos == null) {
                    break OK_MAP;
                } else {
                    return getFsFileNavigationDo(string, parent, fsFileNavigationDo, fsFileNavigationDos, nodeName, "okFsMap", flag);
                }

            }

            if (!fsMap.isEmpty()) {

                List<FsFileNavigationDo> fsFileNavigationDos = fsMap.get(string);
                if (fsFileNavigationDos == null) {
                    String s = string.substring(0, string.lastIndexOf("\\"));
                    if (!StrUtil.isEmpty(s)) {
                        return getNavigation(s, okFs, fs, parent, nodeName, flag);
                    }
                } else {
                    return getFsFileNavigationDo(string, parent, fsFileNavigationDo, fsFileNavigationDos, nodeName, "fsMap", flag);
                }


            } else {// 新的子节点， 父节点已经存。
                String s = string.substring(0, string.lastIndexOf("\\"));
                if (!StrUtil.isEmpty(s)) {
                    return getNavigation(s, okFs, fs, parent, nodeName, flag);
                }
            }

        }

        return fsFileNavigationDo;
    }

    /**
     * 上级节点赋值id 作为 当前节点PID
     *
     * @param string              父级节点路径
     * @param p                   父级节点名称
     * @param fsFileNavigationDo  当前节点文件树信息
     * @param fsFileNavigationDos 已有节点文件树信息集合
     * @param nodeName            当前节点名称
     * @param type                数据类型类型
     * @param flag
     * @return 当前节点信息
     */
    private FsFileNavigationDo getFsFileNavigationDo(String string,
                                                     String p,
                                                     FsFileNavigationDo fsFileNavigationDo,
                                                     List<FsFileNavigationDo> fsFileNavigationDos,
                                                     String nodeName,
                                                     String type, boolean flag) {

        Map<String, FsFileNavigationDo> ndMap;
        ndMap = Optional.of(fsFileNavigationDos).orElse(new ArrayList<>())
                .stream()
                .collect(Collectors.toMap(FsFileNavigationDo::getNodename, Function.identity()));
        FsFileNavigationDo fsnDo = ndMap.get(nodeName);
        if (ObjectUtil.isEmpty(fsnDo)) {
            fsnDo = ndMap.get(p);
            fsFileNavigationDo.setRemark(string + "\\" + nodeName);
        } else {
            if ("okFsMap".equals(type)) {
                if (!flag) {
                    return null;
                } else {
                    fsFileNavigationDo.setRoption("已有结点数据，仅挂载文件");
                }
            }
            fsFileNavigationDo.setRemark(string);
        }

        log.info("获取到父级节点 -> {}", fsnDo);
        fsFileNavigationDo.setPid(fsnDo.getId());

        return fsFileNavigationDo;
    }

    private FileItemDto initWaterMarkAKNK(List<File> files) {
        // 水印处理
        for (File file : files) {
            if ("pdf".equals(file.getName().substring(file.getName().lastIndexOf(".") + 1))) {
                com.aspose.pdf.Document doc = new com.aspose.pdf.Document(file.getAbsolutePath());
                addWatermarkPDF(doc, file.getAbsolutePath(), 1);
            }

        }
        // 上传文件
        FileItemDto fileItemDto = uploadFile(files);

        if (fileItemDto == null) {
            return null;
        }
        return fileItemDto;
    }

    // 数据上传
    private FileItemDto uploadFile(List<File> files) {
        FileItemDto fileItemDto = new FileItemDto();
        // 上传失败数据
        List<InportErrorFileVo> inportErrorFileVos = new ArrayList<>();

        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        List<SysFileItemDo> sysFileItemDos = new ArrayList<>();
        // 获取token
        String token = request.getHeader("token");

        if (StrUtil.isEmpty(token)) {
            log.info("---> token 未获取到。");
            return null;
        }

        // 上传
        for (File file : files) {

            InportErrorFileVo inportErrorFileVo = new InportErrorFileVo();

            if (file.getAbsolutePath().contains(".pdf") ||
                    file.getAbsolutePath().contains("doc") ||
                    file.getAbsolutePath().contains(".docx") ||
                    file.getAbsolutePath().contains(".xls") ||
                    file.getAbsolutePath().contains(".xlsx")) {

                HashMap<String, Object> paramMap = new HashMap<>();
                //文件上传只需将参数中的键指定（默认file），值设为文件对象即可，对于使用者来说，文件上传与普通表单提交并无区别
                paramMap.put("file", file);
                String result = HttpRequest.post("http://localhost:9000/common/sys/sysfile/upload?platform=ic-pc&foldercode=file_system&relationid=0")
                        .header("token", token)
                        .header(Header.CONTENT_TYPE, "multipart/form-data")
                        .form(paramMap)
                        .timeout(20000)
                        .execute().body();
                R re = JSONObject.parseObject(result, R.class);
                SysFileItemDo sysFileItemDo = BeanUtil.copyProperties(re.getData(), SysFileItemDo.class);
                if (sysFileItemDo != null) {
                    sysFileItemDos.add(sysFileItemDo);
                } else {
                    // 上传失败文件
                    inportErrorFileVo.setName(file.getName());
                    inportErrorFileVo.setPath(file.getAbsolutePath());
                    inportErrorFileVo.setType(file.getName().substring(file.getName().lastIndexOf(".") + 1));
                    inportErrorFileVo.setMsg(re.getMsg());
                    inportErrorFileVos.add(inportErrorFileVo);
                }
            } else {
                // 不符合 文件管理系统文件
                inportErrorFileVo.setName(file.getName());
                inportErrorFileVo.setPath(file.getAbsolutePath());
                inportErrorFileVo.setType(file.getName().substring(file.getName().lastIndexOf(".") + 1));
                inportErrorFileVo.setMsg("不属于pdf/doc/docx/xls/xlsx文件类型");
                inportErrorFileVos.add(inportErrorFileVo);
            }

        }
        fileItemDto.setFileItemDoList(sysFileItemDos);
        fileItemDto.setInportErrorFileVos(inportErrorFileVos);

        return fileItemDto;
    }

    public void exportErrorFile(List<InportErrorFileVo> inportErrorFileVos, HttpServletResponse response) {
        log.info(" 开始导出【文件失败报告】 ...");
        // 创建Excel文档
        Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams("文件初始化失败表", "文件信息"),
                InportErrorFileVo.class,
                inportErrorFileVos);
        // 设置响应头
        response.setContentType("application/vnd.ms-excel");
        response.setHeader("Content-Disposition", "attachment; filename=FileErrorMsg.xlsx");
        response.setStatus(200);


        // 将Excel文档写入响应流中
        try {
            ServletOutputStream outputStream = response.getOutputStream();
            workbook.write(outputStream);
            outputStream.flush();
            outputStream.close();
            log.info(" 导出完成");
        } catch (Exception e) {
            e.printStackTrace();
            log.error(e.getMessage());
        }

    }

    /**
     * 遍历所有的文件夹
     * 如果不是文件夹， 则进行文件处理。
     * 是文件夹 ，  那么进行递归。
     *
     * @param folderPath
     * @param filePathMap
     */
    private Map<String, List<File>> initFile(String folderPath, HashMap<String, List<File>> filePathMap) {

        List<File> fileList = new ArrayList<>();
        String filePath = "";
        // 给传入的文件路径创建File类
        File file = new File(folderPath);

        // 判断 是否是一个文件夹
        if (file.isDirectory()) {
            log.info("-----------------------------");
            log.info("文件夹路径 -> {}", file.getPath());
            // 拿到文件夹下所有的文件
            File[] files = file.listFiles();
            // 遍历所有文件
            if (files != null && files.length > 0) {
                for (File file1 : files) {
                    if (file1.isDirectory()) {
                        // 是文件夹
                        // 递归
                        initFile(file1.getAbsolutePath(), filePathMap);
                    } else {
                        // 文件
                        filePath = file1.getParent();
                        fileList.add(file1);
                        log.info("文件路径 -> {}", file1.getAbsolutePath());
                    }
                }
                if (!fileList.isEmpty()) {
                    filePathMap.put(filePath, fileList);
                }
            }
        } else { // 是一个文件
            // 文件操作
            fileList.add(file);
            filePathMap.put(file.getAbsolutePath(), fileList);
            log.info("文件路径 -> {}", file.getPath());
        }
        return filePathMap;
    }

    /**
     * Excel导入
     *
     * @param cellFilePath   文件路径
     * @param sysFileItemDos 附件id
     */
    private void importExcel(List<Path> cellFilePath, List<SysFileItemDo> sysFileItemDos) {
        //读取文件
        for (Path path : cellFilePath) {
            File file = path.toFile();
            ExcelReader reader = ExcelUtil.getReader(file);
            List<org.apache.poi.ss.usermodel.Sheet> sheets = reader.getSheets();
            Sheet firstSheet = sheets.get(0);
            // 获取最后一行数据
            int lastRowNum = firstSheet.getLastRowNum();
            if (lastRowNum == 0) {
                throw new RuntimeException("没有文件初始化信息。");
            }
            List<FsShiftDto> excelData;

            try {
                excelData = getExcelDate(lastRowNum, firstSheet);
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException(e.getMessage());
            }

            if (excelData.isEmpty()) {
                log.info("excel中 无数据");
                throw new RuntimeException(path.getFileName() + ",中没有维护数据。");
            }

            // 校验并保存
            try {
                List<FsFileDataListDo> materialEntities = new ArrayList<>();
                // vo转实体
                voConvertDo(excelData, materialEntities, sysFileItemDos);
//                // 数据存表
//                dataSave(materialEntities);
            } catch (RuntimeException e) {
                e.printStackTrace();
                throw new RuntimeException(e.getMessage());
            }

        }

    }

    /**
     * excel数据转换成实体
     *
     * @param excelData        excel数据
     * @param materialEntities 实体对象
     * @param sysFileItemDos
     */
    private void voConvertDo(List<FsShiftDto> excelData, List<FsFileDataListDo> materialEntities, List<SysFileItemDo> sysFileItemDos) {

        // 附件id转Map
        Map<String, SysFileItemDo> fileItemDoMap = sysFileItemDos.stream().collect(Collectors.toMap(SysFileItemDo::getExtends2, Function.identity()));


        // 文件树状数据筛选
        Map<String, List<FsShiftDto>> listMap = excelData.stream().collect(Collectors.groupingBy(FsShiftDto::getFloorTreeOne));

        ArrayList<String> nodeName = new ArrayList<>(listMap.keySet());

        // 树状文件id,梳妆文件名
        Map<String, String> nodeNameMap = fsFileNavigationService.getAllByNodeName(nodeName);

        //  字典信息
        List<FileFloorDto> fileFloorDtos = fsFileDataListDao.getDict();

        Map<String, String> dictMap = new HashMap<>();

        for (FileFloorDto fileFloorDto : fileFloorDtos) {
            if (!dictMap.containsKey(fileFloorDto.getFName())) {
                dictMap.put(fileFloorDto.getFName(), fileFloorDto.getFValue());
            }
            if (!dictMap.containsKey(fileFloorDto.getPName())) {
                dictMap.put(fileFloorDto.getPName(), fileFloorDto.getPValue());
            }
            if (!dictMap.containsKey(fileFloorDto.getName())) {
                dictMap.put(fileFloorDto.getName(), fileFloorDto.getValue());
            }
        }

        materialEntities = new ArrayList<>();
        //数据转化
        for (FsShiftDto fsShiftDto : excelData) {
            FsFileDataListDo fsFileDataListDo = new FsFileDataListDo();
            // 赋值文件树id
            if (fsShiftDto.getFloorTreeFive() != null && !"/".equals(fsShiftDto.getFloorTreeFive())) {
                fsFileDataListDo.setDocumenttreeid(nodeNameMap.get(fsShiftDto.getFloorTreeFive()));
            } else if (fsShiftDto.getFloorTreeFour() != null && !"/".equals(fsShiftDto.getFloorTreeFour())) {
                fsFileDataListDo.setDocumenttreeid(nodeNameMap.get(fsShiftDto.getFloorTreeFour()));
            } else if (fsShiftDto.getFloorTreeThree() != null && !"/".equals(fsShiftDto.getFloorTreeThree())) {
                fsFileDataListDo.setDocumenttreeid(nodeNameMap.get(fsShiftDto.getFloorTreeThree()));
            } else if (fsShiftDto.getFloorTreeTwo() != null && !"/".equals(fsShiftDto.getFloorTreeTwo())) {
                fsFileDataListDo.setDocumenttreeid(nodeNameMap.get(fsShiftDto.getFloorTreeTwo()));
            } else {
                fsFileDataListDo.setDocumenttreeid(nodeNameMap.get(fsShiftDto.getFloorTreeOne()));
            }

            // 给文件层次、 部门、 前缀 赋值
            if (fsShiftDto.getFileFloor() != null && !"/".equals(fsShiftDto.getFileFloor())) {
                fsFileDataListDo.setVariety(dictMap.get(fsShiftDto.getFileFloor()));
            }
            if (fsShiftDto.getDeep() != null && !"/".equals(fsShiftDto.getDeep())) {
                fsFileDataListDo.setDepartment(dictMap.get(fsShiftDto.getFileFloor() + "#" + fsShiftDto.getDeep()));
            }
            if (fsShiftDto.getCateGory() != null && !"/".equals(fsShiftDto.getCateGory())) {
                if (dictMap.get(fsShiftDto.getFileFloor() + "#" + fsShiftDto.getCateGory()) != null) {
                    fsFileDataListDo.setCategory(dictMap.get(fsShiftDto.getFileFloor() + "#" + fsShiftDto.getCateGory()));
                } else if (dictMap.get(fsShiftDto.getFileFloor() + "#" + fsShiftDto.getDeep() + "#" + fsShiftDto.getCateGory()) != null) {
                    fsFileDataListDo.setCategory(dictMap.get(fsShiftDto.getFileFloor() + "#" + fsShiftDto.getDeep() + "#" + fsShiftDto.getCateGory()));
                }
            }

            // 文件编号赋值
            if (fsShiftDto.getFileNumber() != null && !"/".equals(fsShiftDto.getFileNumber())
                    && fsShiftDto.getCateGory() != null && !"/".equals(fsShiftDto.getCateGory())) {
                fsFileDataListDo.setFileencoding(fsShiftDto.getCateGory() + fsShiftDto.getFileNumber());
            }

            // 文件版本赋值
            if (fsShiftDto.getFileVersion() != null && !"/".equals(fsShiftDto.getFileVersion())) {
                Integer version = Integer.valueOf(fsShiftDto.getFileVersion());
                if (version > 10) {
                    fsFileDataListDo.setSerialnumber("0" + version);
                } else {
                    fsFileDataListDo.setSerialnumber("00" + version);
                }
            }

            // 文件更改单号赋值
            if (fsShiftDto.getFileUpdateNo() != null && !"/".equals(fsShiftDto.getFileUpdateNo())) {
                fsFileDataListDo.setChangenumber(fsShiftDto.getFileUpdateNo());
            }

            // 附件ID赋值
            SysFileItemDo sysFileItemDo = fileItemDoMap.get(fsFileDataListDo.getFileencoding());
            if (ObjectUtil.isNotEmpty(sysFileItemDo)) {
                fsFileDataListDo.setFilepdf(sysFileItemDo.getFilename() + "-;-" + sysFileItemDo.getId());
            }

            // 文件名赋值
            if (fsShiftDto.getFileName() != null && !"/".equals(fsShiftDto.getFileName())) {
                fsFileDataListDo.setFilename(fsShiftDto.getFileName());
            }

            //ID赋值
            fsFileDataListDo.setId(SecurityUtil.getUUID());
            fsFileDataListDo.setCreatedate(new Date());
            fsFileDataListDo.setModifydate(new Date());
            fsFileDataListDo.setFilestate("1");
            fsFileDataListDo.setCurrentstate("1");
        }


    }

    /**
     * 从Excel 中 获取数据
     *
     * @param lastRowNum 最后一行
     * @param firstSheet 第一页数据
     * @return
     */
    private List<FsShiftDto> getExcelDate(int lastRowNum, Sheet firstSheet) {
        // 解析数据
        Row dataRow;

        // 树状第一层
        Cell floorTreeOne;

        // 树状第二层
        Cell floorTreeTwo;

        // 第三层
        Cell floorTreeThree;

        // 第四层
        Cell floorTreeFour;

        // 第五层
        Cell floorTreeFive;

        // 是否EHS
        Cell isEHS;

        // 文件编号
        Cell fileNumber;

        // 文件版本号
        Cell fileVersion;

        // 文件更改单号
        Cell fileUpdateNo;

        // 文件层次
        Cell fileFloor;

        // 部门
        Cell deep;

        //前缀
        Cell cateGory;


        //文件名称
        Cell fileName;

        //文件说明
        Cell fileDescription;

        // 解析并转换数据
        List<FsShiftDto> fsShiftDtos = new ArrayList<>();
        FsShiftDto fsShiftDto;

        for (int i = 2; i <= lastRowNum; i++) {
            // 获取数据行
            dataRow = firstSheet.getRow(i);

            floorTreeOne = dataRow.getCell(0);
            floorTreeTwo = dataRow.getCell(1);
            floorTreeThree = dataRow.getCell(2);
            floorTreeFour = dataRow.getCell(3);
            floorTreeFive = dataRow.getCell(4);

            isEHS = dataRow.getCell(5);
            fileNumber = dataRow.getCell(6);
            fileVersion = dataRow.getCell(7);
            fileUpdateNo = dataRow.getCell(8);
            fileFloor = dataRow.getCell(9);
            deep = dataRow.getCell(10);
            cateGory = dataRow.getCell(11);
            fileName = dataRow.getCell(12);
            fileDescription = dataRow.getCell(13);

            // fsShiftDto赋值
            fsShiftDto = new FsShiftDto();
            fsShiftDto.setFloorTreeOne(getCellValue(floorTreeOne, "一级目录名称", null));
            fsShiftDto.setFloorTreeTwo(getCellValue(floorTreeTwo, "二级目录名称", null));
            fsShiftDto.setFloorTreeThree(getCellValue(floorTreeThree, "三级目录名称", null));
            fsShiftDto.setFloorTreeFour(getCellValue(floorTreeFour, "四级目录名称", null));
            fsShiftDto.setFloorTreeFive(getCellValue(floorTreeFive, "五级目录名称", null));
            fsShiftDto.setIsEHS(getCellValue(isEHS, "是否EHS", null));
            fsShiftDto.setFileNumber(getCellValue(fileNumber, "*文件编号", null));
            fsShiftDto.setFileVersion(getCellValue(fileVersion, "*文件版本号", null));
            fsShiftDto.setFileUpdateNo(getCellValue(fileUpdateNo, "*文件更改单号", null));
            fsShiftDto.setFileFloor(getCellValue(fileFloor, "*文件层次", null));
            fsShiftDto.setDeep(getCellValue(deep, "*部门", null));
            fsShiftDto.setCateGory(getCellValue(cateGory, "*前缀", null));
            fsShiftDto.setFileName(getCellValue(fileName, "*文件名称", null));
            fsShiftDto.setFileDescription(fileDescription.getStringCellValue() == null ? "" : fileDescription.getStringCellValue());

            fsShiftDtos.add(fsShiftDto);
        }


        return fsShiftDtos;
    }


    /**
     * 获取单元格数据
     *
     * @return
     */
    private String getCellValue(Cell currentCell, String cellName, DecimalFormat format) {
        if (currentCell == null || CellType._NONE.equals(currentCell.getCellType()) || CellType.ERROR.equals(currentCell.getCellType())) {
            throw new RuntimeException(String.format("存在未填写%s的单元格请检查模板", cellName));
        }
        String cellValue = "";
        if (CellType.STRING.equals(currentCell.getCellType())) {
            cellValue = currentCell.getStringCellValue();
        }
        if (CellType.NUMERIC.equals(currentCell.getCellType()) || CellType.FORMULA.equals(currentCell.getCellType())) {
            cellValue = format.format(currentCell.getNumericCellValue());
        }
        if (StringUtils.isBlank(cellValue)) {
            throw new RuntimeException(String.format("存在未填写%s的单元格请检查模板", cellName));
        }
        return cellValue;
    }


    private List<SysFileItemDo> pdfFileTodo(List<Path> pdfFilePath) {
        // 水印处理
        initWaterMark(pdfFilePath);

        // 上传文件
        return uploadPdf(pdfFilePath);
    }

    /**
     * 上传PDF文件
     *
     * @param pdfFilePath 文件路径
     * @return List<SysFileItemDo>  附件表信息
     */
    private List<SysFileItemDo> uploadPdf(List<Path> pdfFilePath) {
        List<SysFileItemDo> sysFileItemDos = new ArrayList<>();

        // 获取token
        SysUserDo userDo = sysUserService.getById(FileSystemUtils.getCurrentUser().getId());
        Map<String, Object> authMap = new HashMap<>();
        authMap.put("id", userDo.getId());
        authMap.put("username", userDo.getAccount());
        authMap.put("name", userDo.getName());
        authMap.put("expire", -1);
        String token = loginAuth.getTokenInfo(authMap);

        if (StrUtil.isEmpty(token)) {
            log.info("---> token 未获取到。");
            return null;
        }

        for (Path path : pdfFilePath) {

            HashMap<String, Object> paramMap = new HashMap<>();
            //文件上传只需将参数中的键指定（默认file），值设为文件对象即可，对于使用者来说，文件上传与普通表单提交并无区别
            paramMap.put("file", FileUtil.file(path.toString()));
            String result = HttpRequest.post("http://localhost:9000/common/sys/sysfile/upload?platform=ic-pc&foldercode=file_system&relationid=0")
                    .header("token", token)
                    .header(Header.CONTENT_TYPE, "multipart/form-data")
                    .form(paramMap)
                    .timeout(20000)
                    .execute().body();

            JSONObject jsonObject = JSONObject.parseObject(result);
        }


        return sysFileItemDos;
    }

    /**
     * File 文件转 MultipartFile
     *
     * @param file
     * @return
     */
    private MultipartFile getMultipartFile(File file) {
        FileItem item = new DiskFileItemFactory().createItem("file"
                , MediaType.APPLICATION_PDF_VALUE
                , true
                , file.getName());
        try (InputStream input = new FileInputStream(file);
             OutputStream os = item.getOutputStream()) {
            // 流转移
            IOUtils.copy(input, os);
        } catch (Exception e) {
            throw new IllegalArgumentException("Invalid file: " + e, e);
        }

        return new CommonsMultipartFile(item);
    }

    /**
     * 添加受控水印
     *
     * @param pdfFilePath 文件路径
     * @return
     */
    private List<Path> initWaterMark(List<Path> pdfFilePath) {
        pdfFilePath.forEach(path -> {
            com.aspose.pdf.Document doc = new com.aspose.pdf.Document(path.toString());
            addWatermarkPDF(doc, path.toString(), 1);
        });
        return pdfFilePath;
    }

    /**
     * 将历史数据存入主表，主表数据存入历史 ， 历史发放人员存入培训人员主表
     *
     * @param fsFileDataListDo         关闭流程的主表信息，需要存入历史表
     * @param fsFileDataListRecordDo   上个版本的数据信息，需要存入主表
     * @param fsTrainingStaffRecordDos 生效的培训人员
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public R saveFileData(FsFileDataListDo fsFileDataListDo, FsFileDataListRecordDo fsFileDataListRecordDo, List<FsTrainingStaffRecordDo> fsTrainingStaffRecordDos) {
        try {
            //关闭流程的主表信息，需要存入历史表
            FsFileDataListRecordDo newFdrData = new FsFileDataListRecordDo();
            BeanUtil.copyProperties(fsFileDataListDo, newFdrData);
            String masterFDId = newFdrData.getId();
            newFdrData.setId(SecurityUtil.getUUID());
            newFdrData.setMasterid(masterFDId);
            // 存入历史表
            fsFileDataListRecordService.getBaseMapper().insert(newFdrData);


            //上个版本的数据信息，需要存入主表
            FsFileDataListDo newFdData = new FsFileDataListDo();
            BeanUtil.copyProperties(fsFileDataListRecordDo, newFdData);
            newFdData.setId(fsFileDataListRecordDo.getMasterid());
            baseMapper.updateById(newFdData);

            ArrayList<FsTrainingStaffDo> fsTrainingStaffDos = new ArrayList<>();
            for (FsTrainingStaffRecordDo fsTrainingStaffRecordDo : fsTrainingStaffRecordDos) {
                FsTrainingStaffDo fsTrainingStaffDo = new FsTrainingStaffDo();
                BeanUtil.copyProperties(fsTrainingStaffRecordDo, fsTrainingStaffDo);
                fsTrainingStaffDo.setMasterid(newFdData.getId());
                fsTrainingStaffDos.add(fsTrainingStaffDo);
            }
            fsTrainingStaffService.saveBatch(fsTrainingStaffDos);
            return R.ok();
        } catch (Exception e) {
            e.printStackTrace();
            // 程序异常， 手动回滚事务
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return R.error(e.getMessage());
        }

    }


    /**
     * 拿到 表单中的文件id,文件路径,文件格式, 水印
     *
     * @param fileIdDtos
     * @return
     */
    private List<FileDto> getFilePath(List<FileIdDto> fileIdDtos, Integer type) {

        // 附件id
        List<String> fileIds = new ArrayList<>();
        fileIdDtos.forEach(dto -> {
            Map<String, List<String>> ids = dto.ids();
//            fileIds.addAll(ids.get("word"));
            fileIds.addAll(ids.get("pdf"));
        });

        if (fileIds.size() == 0) {
            return null;
        }
        // 获取 文件 绝对路径
        List<FileDto> fileDtos = baseMapper.getFilePath(fileIds);


        // 完善FileDto
        Map<String, String> map = new HashMap<>();

        List<FileFormDto> fileFormDtos = new ArrayList<>();

        // 转换附件id
        fileIdDtos.forEach(fileIdDto -> {
            FileFormDto fileFormDto = new FileFormDto();
            fileFormDto.setFormId(fileIdDto.getFormId());
            fileFormDto.setFlag(fileIdDto.getFlag());
//
//            String wordId = fileIdDto.getWordId();
//            String[] split = wordId.split("-;-");
//            fileFormDto.setWordId(Arrays.asList(split[1].split(",")));

            String pdfId = fileIdDto.getPdfId();
            String[] strings = pdfId.split("-;-");
            fileFormDto.setPdfId(Arrays.asList(strings[1].split(",")));

            fileFormDtos.add(fileFormDto);
        });

        for (FileFormDto fileFormDto : fileFormDtos) {
            Map<String, String> map1 = fileFormDto.fileFromIdMap();
            map.putAll(map1);
        }

        for (FileDto fileDto : fileDtos) {
            String formIdAndFlag = map.get(fileDto.getFileId());
            List<String> list = Arrays.asList(formIdAndFlag.split(";"));
            if ("history".equals(list.get(1))) {
                fileDto.setHistoryFormId(list.get(1));
            } else {
                fileDto.setFormId(list.get(0));
            }
        }
        return fileDtos;
    }


    /**
     * 水印处理 -- pdf文件
     *
     * @param fileDtos 待处理文件
     * @param type     处理方式 :   1- 受控   2- 更新  3- 作废
     * @param manual   自动/手动
     */
    private void dealWithPDFWatermarking(List<FileDto> fileDtos, Integer type, String manual) throws Exception {
        // 循环处理文件
        for (FileDto fileDto : fileDtos) {

            com.aspose.pdf.Document doc = new com.aspose.pdf.Document(fileDto.getFileFullPath());
            try {

                // 水印处理
                if (ObjectUtil.isNotEmpty(doc)) {
                    switch (type) {
                        case 1:
                            // 添加水印
                            addWatermarkPDF(doc, fileDto.getFileFullPath(), type);
                            break;

                        case 2:
                            // 变更水印
                            if ("manual".equals(manual)) {
                                if (StrUtil.isNotBlank(fileDto.getFormId())) {
                                    updateWatermarkPDF(doc, fileDto.getFileFullPath(), 1);
                                } else if (StrUtil.isNotBlank(fileDto.getHistoryFormId())) {
                                    updateWatermarkPDF(doc, fileDto.getFileFullPath(), type);
                                }
                            } else {
                                updateWatermarkPDF(doc, fileDto.getFileFullPath(), type);
                            }

                            break;
                        case 3:
                            updateWatermarkPDF(doc, fileDto.getFileFullPath(), type);
                            break;
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                doc.close();
                System.gc();
            }

        }
    }


    /**
     * PDF 变更水印
     *
     * @param doc          pdf文件
     * @param fileFullPath 文件路径
     * @param type         处理方式 :   1- 受控   2- 更新  3- 作废
     */
    private void updateWatermarkPDF(com.aspose.pdf.Document doc, String fileFullPath, Integer type) throws Exception {

        log.info(" 删除水印 -> {}", doc);
        for (int i = 1; i <= doc.getPages().size(); i++) {
            // 删除 水印
            if (doc.getPages().get_Item(i).getArtifacts().size() > 0) {
                if (doc.getPages().get_Item(i).getArtifacts().get_Item(1).getSubtype() == com.aspose.pdf.Artifact.ArtifactSubtype.Watermark) {
                    for (int j = 1; j <= doc.getPages().get_Item(i).getArtifacts().size(); j++) {
                        doc.getPages().get_Item(i).getArtifacts().delete(doc.getPages().get_Item(i).getArtifacts().get_Item(j));
                    }
                }
            } else {
                break;
            }
        }

//        save result pdf to file
        doc.save(fileFullPath, com.aspose.pdf.SaveFormat.Pdf);
//        doc.save(fileFullPath);
        doc.close();


        com.aspose.pdf.Document pdfDoc = new com.aspose.pdf.Document(fileFullPath);
        this.addWatermarkPDF(pdfDoc, fileFullPath, type);
    }

    /**
     * PDF 添加水印
     *
     * @param doc          pdf文件
     * @param fileFullPath 文件路径
     * @param type         处理方式 :   1- 受控   2- 更新  3- 作废
     */
    private void addWatermarkPDF(com.aspose.pdf.Document doc, String fileFullPath, Integer type) {
        String watermarkText;
        if (type == 1) {
            watermarkText = "受控" + DateUtil.format(new Date(), "yyyy-MM-dd");
        } else if (type == 2) {
            watermarkText = "失效" + DateUtil.format(new Date(), "yyyy-MM-dd");
        } else {
            watermarkText = "作废" + DateUtil.format(new Date(), "yyyy-MM-dd");
        }
        // 水印信息
//        FormattedText formattedText = new FormattedText(watermarkText, Color.GRAY, FontStyle.CjkFont, EncodingType.Identity_h, true, 50.0F);

//        // 水印类
//        WatermarkArtifact artifact = new WatermarkArtifact();
////        artifact.setText(formattedText);
//        artifact.setArtifactHorizontalAlignment(HorizontalAlignment.Center);
//        artifact.setArtifactVerticalAlignment(VerticalAlignment.Center);
////        artifact.setRotation(45);
//        artifact.setOpacity(0.2);
//        artifact.setBackground(false);
//
//        // 指定字体
//        artifact.setTextAndState(watermarkText, new TextState("Microsoft YaHei UI", 25.0F));
        log.info("---> 添加水印 , {}", fileFullPath);
//        // 水印信息
//        TextStamp textStamp = new TextStamp(watermarkText);
//
//        textStamp.setBackground(true);
//
//        textStamp.setXIndent(200);
//
//        textStamp.setYIndent(500);
//
//        textStamp.setRotate(Rotation.None);
//
//        textStamp.setOutlineOpacity(0.5);
//
//        textStamp.setStampId(100);
//
//        textStamp.getTextState().setFont(FontRepository.findFont("Arial"));
//
//        textStamp.getTextState().setFontSize(34.0F);
//
//        textStamp.getTextState().setFontStyle(FontStyles.Bold);
//
//        textStamp.getTextState().setFontStyle(FontStyles.Italic);
//
//        textStamp.getTextState().setForegroundColor(com.aspose.pdf.Color.getGray());


        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                // 页面插入水印
                for (int i = 1; i <= doc.getPages().size(); i++) {
                    WatermarkArtifact artifact = new WatermarkArtifact();
                    artifact.setArtifactHorizontalAlignment(HorizontalAlignment.Center);
                    artifact.setArtifactVerticalAlignment(VerticalAlignment.Center);
                    artifact.setOpacity(0.2);
                    artifact.setBackground(false);

                    // 指定字体
                    artifact.setTextAndState(watermarkText, new TextState("Microsoft YaHei UI", 25.0F));
                    // 倾斜角度
                    artifact.setRotation(45.0);
                    doc.getPages().get_Item(i).getArtifacts().add(artifact);
                }

            }
        });

        t.run();
        t.stop();


        // 保存文档
        doc.save(fileFullPath);
        doc.close();

        log.info(" --->  添加完成，并保存");
    }


    /**
     * 水印处理 -- word文件
     *
     * @param fileDtos 待处理文件
     * @param type     处理方式 :   1- 受控   2- 更新  3- 作废
     */
    private void dealWithWordWatermarking(List<FileDto> fileDtos, Integer type) throws Exception {

        // 循环处理文件
        for (FileDto fileDto : fileDtos) {

            Document doc = new Document(fileDto.getFileFullPath());

            // 水印处理
            if (ObjectUtil.isNotEmpty(doc)) {
                switch (type) {
                    case 1:
                        // 添加水印
                        addWatermarkWord(doc, fileDto.getFileFullPath(), fileDto.getFileType(), type);
                        break;

                    case 2:
                    case 3:
                        // 变更水印
                        updateWatermarkWord(doc, fileDto.getFileFullPath(), fileDto.getFileType(), type);
                        break;
                }
            }

        }
    }


    /**
     * @param doc      文件
     * @param fullPath 文件路径
     * @param fileType 文件格式
     * @param type     处理类型
     * @throws Exception
     */
    private void addWatermarkWord(Document doc, String fullPath, String fileType, Integer type) throws Exception {

        String watermarkText = "";
        if (type == 1) {
            watermarkText = "受控" + DateUtil.format(new Date(), "yyyy-MM-dd");
        } else if (type == 2) {
            watermarkText = "失效" + DateUtil.format(new Date(), "yyyy-MM-dd");
        } else {
            watermarkText = "作废" + DateUtil.format(new Date(), "yyyy-MM-dd");
        }

        // 添加水印信息
        TextWatermarkOptions textWatermarkOptions = new TextWatermarkOptions();
        TextWatermarkOptions watermarkOptions = new TextWatermarkOptions();
        watermarkOptions.setFontSize(36);
        watermarkOptions.setFontFamily("Arial");
        watermarkOptions.setColor(Color.GRAY);
        watermarkOptions.setLayout(WatermarkLayout.DIAGONAL);
        watermarkOptions.isSemitrasparent(true);

        // 插入水印
        doc.getWatermark().setText(watermarkText, textWatermarkOptions);

        // 更新文档
        if ("pdf".equals(fileType)) {
            doc.save(fullPath.replace("docx", "pdf"), SaveFormat.PDF);
        } else {
            doc.save(fullPath);

        }
    }


    /**
     * 变更水印
     */
    private void updateWatermarkWord(Document doc, String fullPath, String fileType, Integer type) throws Exception {

        // 删除水印
        doc.getWatermark().remove();
        doc.save(fullPath);
        // 新增 水印
        this.addWatermarkWord(doc, fullPath, fileType, type);
    }
}
