package com.qmth.paper.library.business.templete.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.google.common.collect.Lists;
import com.itextpdf.io.image.ImageDataFactory;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Image;
import com.qmth.boot.api.exception.ApiException;
import com.qmth.boot.tools.excel.ExcelReader;
import com.qmth.boot.tools.excel.enums.ExcelType;
import com.qmth.boot.tools.excel.model.DataMap;
import com.qmth.paper.library.business.bean.dto.CodeNameEnableDisabledValue;
import com.qmth.paper.library.business.bean.dto.ExamStudentImport;
import com.qmth.paper.library.business.bean.dto.ExamStudentParseDto;
import com.qmth.paper.library.business.bean.result.DocManageDetailResult;
import com.qmth.paper.library.business.bean.result.PictureManageDetailResult;
import com.qmth.paper.library.business.entity.PaperLibrary;
import com.qmth.paper.library.business.entity.PaperScanTask;
import com.qmth.paper.library.business.mapper.DocManageMapper;
import com.qmth.paper.library.business.service.*;
import com.qmth.paper.library.business.templete.service.TaskLogicService;
import com.qmth.paper.library.common.bean.dto.excel.DescribeImportDto;
import com.qmth.paper.library.common.bean.dto.excel.SysOrgImportDto;
import com.qmth.paper.library.common.bean.dto.excel.SysUserImportDto;
import com.qmth.paper.library.common.bean.params.DownLoadPaperParams;
import com.qmth.paper.library.common.bean.params.DownloadPictureParams;
import com.qmth.paper.library.common.config.DictionaryConfig;
import com.qmth.paper.library.common.contant.SystemConstant;
import com.qmth.paper.library.common.entity.*;
import com.qmth.paper.library.common.enums.ExceptionResultEnum;
import com.qmth.paper.library.common.enums.StoreTypeEnum;
import com.qmth.paper.library.common.enums.UploadFileEnum;
import com.qmth.paper.library.common.lock.LockService;
import com.qmth.paper.library.common.lock.LockType;
import com.qmth.paper.library.common.service.*;
import com.qmth.paper.library.common.util.*;
import com.qmth.paper.library.common.util.excel.ExcelError;
import com.qmth.paper.library.common.util.pdf.ItextDocumentInfo;
import com.qmth.paper.library.common.util.pdf.ItextPdfUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;

import javax.annotation.Resource;
import java.io.*;
import java.lang.reflect.Field;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @Description: 任务处理逻辑impl
 * @Param:
 * @return:
 * @Author: wangliang
 * @Date: 2021/3/29
 */
@Service
public class TaskLogicServiceImpl implements TaskLogicService {
    private final static Logger log = LoggerFactory.getLogger(TaskLogicServiceImpl.class);

    @Resource
    private DictionaryConfig dictionaryConfig;
    @Resource
    private SysOrgService sysOrgService;
    @Resource
    private SysUserService sysUserService;
    @Resource
    private DocManageService docManageService;
    @Resource
    private PaperLibraryCommonService paperLibraryCommonService;
    @Resource
    private FileStoreUtil fileStoreUtil;
    @Resource
    private PaperLibraryService paperLibraryService;
    @Resource
    private PaperScanTaskService paperScanTaskService;
    @Resource
    private ExamStudentService examStudentService;
    @Resource
    private BasicSchoolService basicSchoolService;
    @Resource
    private BasicSemesterService basicSemesterService;
    @Resource
    private BasicExamService basicExamService;
    @Resource
    private PictureManageService pictureManageService;
    @Resource
    private BasicFieldService basicFieldService;
    @Resource
    private LockService lockService;
    @Resource
    private DocManageMapper docManageMapper;


    @Transactional
    @Override
    public Map<String, Object> executeImportSysOrgLogic(Map<String, Object> map) throws IOException, NoSuchFieldException {
        InputStream inputStream = (InputStream) map.get("inputStream");
        List<LinkedMultiValueMap<Integer, Object>> finalList = ExcelUtil.excelReader(inputStream, Lists.newArrayList(SysOrgImportDto.class, DescribeImportDto.class), (finalExcelList, finalColumnNameList, finalExcelErrorList) -> finalExcelList);
        List<String> orgInfoList = new ArrayList<>();
        for (int i = 0; i < finalList.size(); i++) {
            LinkedMultiValueMap<Integer, Object> excelMap = finalList.get(i);
            List<Object> sysOrgImportDtoList = excelMap.get(i);
            assert sysOrgImportDtoList != null;
            if (sysOrgImportDtoList.get(0) instanceof DescribeImportDto) {
                continue;
            }
            map.put("dataCount", sysOrgImportDtoList.size());
            //  处理机构
            if (sysOrgImportDtoList.get(0) instanceof SysOrgImportDto) {
                List<SysOrgImportDto> datasource = sysOrgImportDtoList.stream().map(e -> {
                    SysOrgImportDto sysOrgImportDto = new SysOrgImportDto();
                    BeanUtils.copyProperties(e, sysOrgImportDto);
                    return sysOrgImportDto;
                }).collect(Collectors.toList());
                if (datasource.size() > 0) {
                    datasource.forEach(e -> orgInfoList.add(e.getName()));
                }
            }
        }
        if (orgInfoList.size() > 0) {
            SysUser requestUser = (SysUser) map.get(SystemConstant.SYS_USER);
            sysOrgService.createOrGetOrgByOrgInfo(orgInfoList, requestUser);
        }
        return map;
    }

    @Transactional
    @Override
    public Map<String, Object> executeImportSysUserLogic(Map<String, Object> map) throws Exception {
        InputStream inputStream = (InputStream) map.get("inputStream");
        List<LinkedMultiValueMap<Integer, Object>> finalList = ExcelUtil.excelReader(inputStream, Lists.newArrayList(SysUserImportDto.class, DescribeImportDto.class), (finalExcelList, finalColumnNameList, finalExcelErrorList) -> {
            List<ExcelError> excelErrorTemp = new ArrayList<>();
            Map<String, SysUserImportDto> checkDtoMap = new HashMap<>();

            for (int i = 0; i < finalExcelList.size(); i++) {
                LinkedMultiValueMap<Integer, Object> excelMap = finalExcelList.get(i);
                List<Object> sysUserImportDtoList = excelMap.get(i);
                assert sysUserImportDtoList != null;
                if (sysUserImportDtoList.get(0) instanceof DescribeImportDto) {
                    continue;
                }
                map.put("dataCount", sysUserImportDtoList.size());
                List<SysUserImportDto> duplicateData = new ArrayList<>();
                for (int y = 0; y < Objects.requireNonNull(sysUserImportDtoList).size(); y++) {
                    SysUserImportDto sysUserImportDto = (SysUserImportDto) sysUserImportDtoList.get(y);
                    String name = StringUtils.isNotBlank(sysUserImportDto.getName()) ? sysUserImportDto.getName().trim() : null;
                    String loginName = StringUtils.isNotBlank(sysUserImportDto.getLoginName()) ? sysUserImportDto.getLoginName().trim() : null;
//                    String phoneNumber = StringUtils.isNotBlank(sysUserImportDto.getPhoneNumber()) ? sysUserImportDto.getPhoneNumber().trim() : null;
                    String orgName = StringUtils.isNotBlank(sysUserImportDto.getOrgName()) ? sysUserImportDto.getOrgName().trim() : null;
                    String roleName = StringUtils.isNotBlank(sysUserImportDto.getRoleName()) ? sysUserImportDto.getRoleName().trim() : null;

                    if (checkDtoMap.containsKey(loginName)) {
                        SysUserImportDto primaryCell = checkDtoMap.get(loginName);
                        String priName = primaryCell.getName();
//                        String priPhoneNumber = primaryCell.getPhoneNumber();
                        String priOrgName = primaryCell.getOrgName();
                        if (!Objects.equals(priName, name)) {
                            throw ExceptionResultEnum.ERROR.exception("用户名[" + loginName + "]的用户存在不同的名称");
                        }
//                        if (!Objects.equals(priPhoneNumber, phoneNumber)) {
//                            throw ExceptionResultEnum.ERROR.exception("【账号】 ：" + loginName + "的用户存在不同的手机号");
//                        }
                        if (!Objects.equals(priOrgName, orgName)) {
                            throw ExceptionResultEnum.ERROR.exception("用户名[" + loginName + "]的用户存在不同的组织架构");
                        }
                        String priRoleName = primaryCell.getRoleName();
                        if (SystemConstant.strNotNull(roleName)) {
                            priRoleName = priRoleName + SystemConstant.COMMA + roleName;
                            primaryCell.setRoleName(priRoleName);
                        }
                        duplicateData.add(sysUserImportDto);
                    } else {
                        checkDtoMap.put(loginName, sysUserImportDto);
                    }

                    excelErrorTemp.addAll(ExcelUtil.checkExcelField(sysUserImportDto, y, i));
                    if (Objects.isNull(loginName) || loginName.length() > 30 || !loginName.matches(SystemConstant.REGULAR_EXPRESSION_OF_CODE)) {
                        excelErrorTemp.add(new ExcelError(y + 1, "第" + (i + 1) + "个sheet第" + (y + 1) + "行[账号]不符合输入规范，不为输入中文"));
                    }
                    if (Objects.isNull(name) || name.length() > 30) {
                        excelErrorTemp.add(new ExcelError(y + 1, "第" + (i + 1) + "个sheet第" + (y + 1) + "行[姓名]不符合输入规范"));
                    }
//                    if (Objects.nonNull(phoneNumber) && !phoneNumber.matches(SystemConstant.REGULAR_EXPRESSION_OF_PHONE)) {
//                        excelErrorTemp.add(new ExcelError(y + 1, "excel第" + (i + 1) + "个sheet第" + (y + 1) + "行[电话号码]不符合输入规范"));
//                    }
                }
                sysUserImportDtoList.removeAll(duplicateData);
            }
            if (excelErrorTemp.size() > 0) {
                throw ExceptionResultEnum.ERROR.exception(JSONObject.toJSONString(excelErrorTemp));
            }
            return finalExcelList;
        });
        return sysUserService.executeSysUserImportLogic(finalList, map);
    }


    @Override
    public Map<String, Object> executeDownloadPaperLogic(Map<String, Object> map) {
        List<File> sourceFileList = new ArrayList<>();
        File zipFile = null;
        String rootPath = null;
        String zipLocalRootPath = null;
        try {
            TBTask tbTask = (TBTask) map.get(SystemConstant.TASK);
            DownLoadPaperParams downLoadPaperParams = JSON.parseObject(tbTask.getRemark(), DownLoadPaperParams.class);

            PaperScanTask paperScanTask = paperScanTaskService.getById(downLoadPaperParams.getPaperScanTaskId());

            BasicSchool basicSchool = basicSchoolService.getById(paperScanTask.getSchoolId());
            BasicExam basicExam = basicExamService.getById(paperScanTask.getExamId());
            BasicSemester basicSemester = basicSemesterService.getById(basicExam.getSemesterId());
            String courseNameCode = String.format("%s(%s)", paperScanTask.getCourseName(), paperScanTask.getCourseCode());

            List<DocManageDetailResult> docManageDetailResults = docManageService.listDetail(downLoadPaperParams);
            rootPath = fileStoreUtil.buildPath(UploadFileEnum.DOWNLOAD, true, String.valueOf(System.currentTimeMillis()));
            String dirName = SystemConstant.getUuid() + SystemConstant.ZIP_SUFFIX;
            zipFile = new File(rootPath + dirName);
            if (!zipFile.getParentFile().exists()) {
                zipFile.getParentFile().mkdirs();
            }
            zipLocalRootPath = rootPath + File.separator + basicSchool.getName() + File.separator + basicSemester.getName() + File.separator + basicExam.getName() + File.separator + courseNameCode;
            Map<Long, Integer> successMap = new HashMap<>();
            Map<Long, Integer> errorMap = new HashMap<>();
            for (DocManageDetailResult docManageDetailResult : docManageDetailResults) {
                Long studentId = docManageDetailResult.getStudentId();
                successMap.putIfAbsent(studentId, 0);
                errorMap.putIfAbsent(studentId, 0);
                QueryWrapper<PaperLibrary> queryWrapper = new QueryWrapper<>();
                queryWrapper.lambda().eq(PaperLibrary::getPaperScanTaskId, downLoadPaperParams.getPaperScanTaskId())
                        .eq(PaperLibrary::getStudentId, studentId);
                List<PaperLibrary> paperLibraryList = paperLibraryService.list(queryWrapper);

                ExamStudent examStudent = examStudentService.getById(studentId);
                for (PaperLibrary paperLibrary : paperLibraryList) {
                    List<JSONObject> objectList = JSON.parseArray(paperLibrary.getPath(), JSONObject.class);
                    for (JSONObject jsonObject : objectList) {
                        try {
                            String imagePath = zipLocalRootPath;
                            if (StoreTypeEnum.ROOM.equals(paperScanTask.getStoreType())) {
                                imagePath = imagePath + File.separator + examStudent.getExamRoom();
                            } else if (StoreTypeEnum.CLASS.equals(paperScanTask.getStoreType())) {
                                imagePath = imagePath + File.separator + examStudent.getClassName();
                            } else if (StoreTypeEnum.COURSE.equals(paperScanTask.getStoreType())) {
                                imagePath = imagePath + File.separator + examStudent.getStudentCode();
                            }
                            File file = paperLibraryCommonService.downloadFile(imagePath, jsonObject.getString(SystemConstant.PATH), jsonObject.getString(SystemConstant.UPLOAD_TYPE), jsonObject.getString(SystemConstant.TYPE));
                            sourceFileList.add(file);
                            successMap.put(studentId, successMap.get(studentId) + 1);
                        } catch (FileNotFoundException e) {
                            errorMap.put(studentId, successMap.get(studentId) + 1);
                        }
                    }
                }
            }

            if (!CollectionUtils.isEmpty(sourceFileList)) {
                // 生成清单
                createPaperDownloadExcel(rootPath, docManageDetailResults, successMap, errorMap);

                Zip4jUtil.zipEncryptFile(zipFile.getPath(), rootPath, SystemConstant.ZIP_ENCRYPT_PWD);
                String ossDirName = dirName.replaceAll("\\\\", "/");
                JSONObject jsonObject = new JSONObject();
                if (fileStoreUtil.isOssStore()) {
                    fileStoreUtil.fileUpload(ossDirName, zipFile);
                    jsonObject.put(SystemConstant.PATH, ossDirName);
                    jsonObject.put(SystemConstant.TYPE, SystemConstant.OSS);
                } else {
                    jsonObject.put(SystemConstant.TYPE, SystemConstant.LOCAL);
                    jsonObject.put(SystemConstant.PATH, rootPath + ossDirName);
                }
                jsonObject.put(SystemConstant.UPLOAD_TYPE, UploadFileEnum.FILE);
                tbTask.setExportFilePath(jsonObject.toJSONString());
            }
            map.put(SystemConstant.SIZE, Objects.nonNull(docManageDetailResults) ? docManageDetailResults.size() : 0);
        } catch (
                Exception e) {
            log.error(SystemConstant.LOG_ERROR, e);
            if (e instanceof ApiException) {
                ResultUtil.error((ApiException) e, e.getMessage());
            } else {
                ResultUtil.error(e.getMessage());
            }
        } finally {
            if (Objects.nonNull(rootPath)) {
                ConvertUtil.delFolder(rootPath);
            }
            if (Objects.nonNull(dictionaryConfig.sysDomain()) && fileStoreUtil.isOssStore() && Objects.nonNull(zipFile)) {
                zipFile.delete();
            }
        }
        return map;
    }

    public Map<String, Object> executeDownloadPictureLogic(Map<String, Object> map) {
        List<File> sourceFileList = new ArrayList<>();
        File zipFile = null;
        String rootPath = null;
        String zipLocalRootPath;
        List<PaperLibrary> paperLibraryList;
        try {
            BasicSchool school = basicSchoolService.getById(Long.valueOf(map.get("schoolId").toString()));
            TBTask tbTask = (TBTask) map.get(SystemConstant.TASK);
            DownloadPictureParams downLoadPaperParams = JSON.parseObject(tbTask.getRemark(), DownloadPictureParams.class);
            List<PictureManageDetailResult> studentList = pictureManageService.listPictureResult(downLoadPaperParams);

            rootPath = fileStoreUtil.buildPath(UploadFileEnum.DOWNLOAD, true, String.valueOf(System.currentTimeMillis()));
            String dirName = SystemConstant.getUuid() + SystemConstant.ZIP_SUFFIX;
            zipFile = new File(rootPath + dirName);
            if (!zipFile.getParentFile().exists()) {
                boolean dirSuccess = zipFile.getParentFile().mkdirs();
                if (!dirSuccess) {
                    log.error(String.format("[图片管理],目录创建失败,dirPath:%s", rootPath));
                    throw ExceptionResultEnum.ERROR.exception("目录创建失败");
                }
            }

            //zip文件存放本地根目录
            zipLocalRootPath = rootPath + File.separator + school.getName();
            Map<Long, Integer> successMap = new HashMap<>();
            Map<Long, Integer> errorMap = new HashMap<>();

            for (PictureManageDetailResult student : studentList) {
                Long studentId = student.getStudentId();
                successMap.putIfAbsent(studentId, 0);
                errorMap.putIfAbsent(studentId, 0);

                //考生的所有试卷
                LambdaQueryWrapper<PaperLibrary> queryWrapper = new LambdaQueryWrapper<>();
                queryWrapper.eq(PaperLibrary::getStudentId, studentId);
                paperLibraryList = paperLibraryService.list(queryWrapper);

                ExamStudent examStudent = examStudentService.getById(studentId);
                String courseNameCode = String.format("%s(%s)", examStudent.getCourseName(), examStudent.getCourseCode());
                String imagePath = zipLocalRootPath + File.separator + courseNameCode + File.separator + examStudent.getTeachClass();

                for (PaperLibrary paperLibrary : paperLibraryList) {
                    List<JSONObject> objectList = JSON.parseArray(paperLibrary.getPath(), JSONObject.class);
                    //下载考生试卷
                    for (JSONObject jsonObject : objectList) {
                        try {
                            File file = paperLibraryCommonService.downloadFile(imagePath, jsonObject.getString(SystemConstant.PATH),
                                    jsonObject.getString(SystemConstant.UPLOAD_TYPE), jsonObject.getString(SystemConstant.TYPE));
                            sourceFileList.add(file);
                            successMap.put(studentId, successMap.get(studentId) + 1);
                        } catch (Exception e) {
                            errorMap.put(studentId, errorMap.get(studentId) + 1);
                        }
                    }
                }

            }

            if (!CollectionUtils.isEmpty(sourceFileList)) {
                // 生成清单
                createPictureDownloadExcel(rootPath, studentList, successMap, errorMap);

                Zip4jUtil.zipEncryptFile(zipFile.getPath(), rootPath, SystemConstant.ZIP_ENCRYPT_PWD);
                String ossDirName = dirName.replaceAll("\\\\", "/");
                JSONObject jsonObject = new JSONObject();
                if (fileStoreUtil.isOssStore()) {
                    fileStoreUtil.fileUpload(ossDirName, zipFile);
                    jsonObject.put(SystemConstant.PATH, ossDirName);
                    jsonObject.put(SystemConstant.TYPE, SystemConstant.OSS);
                } else {
                    jsonObject.put(SystemConstant.TYPE, SystemConstant.LOCAL);
                    jsonObject.put(SystemConstant.PATH, rootPath + ossDirName);
                }
                jsonObject.put(SystemConstant.UPLOAD_TYPE, UploadFileEnum.FILE);
                tbTask.setExportFilePath(jsonObject.toJSONString());
            }
            map.put(SystemConstant.SIZE, studentList.size());
        } catch (Exception e) {
            log.error(SystemConstant.LOG_ERROR, e);
            if (e instanceof ApiException) {
                ResultUtil.error((ApiException) e, e.getMessage());
            } else {
                ResultUtil.error(e.getMessage());
            }
        } finally {
            if (Objects.nonNull(rootPath)) {
                ConvertUtil.delFolder(rootPath);
            }
            if (Objects.nonNull(dictionaryConfig.sysDomain()) && fileStoreUtil.isOssStore() && Objects.nonNull(zipFile)) {
                boolean success = zipFile.delete();
                if (!success) {
                    log.warn(String.format("[图片管理],临时zip目录删除失败,zipPath:%s", zipFile.getPath()));
                }
            }
        }
        return map;
    }

    private void createPictureDownloadExcel(String zipLocalRootPath, List<PictureManageDetailResult> list, Map<Long, Integer> successMap,
                                            Map<Long, Integer> errorMap) throws IOException {
        String fileName = "下载记录清单.xlsx";
        XSSFWorkbook wb = new XSSFWorkbook();
        XSSFSheet sheet = wb.createSheet("数据");

        // 表头
        CellStyle headerStyle = wb.createCellStyle();
        headerStyle.setAlignment(HorizontalAlignment.CENTER);
        headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        headerStyle.setBorderRight(BorderStyle.THIN);
        headerStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());
        headerStyle.setBorderLeft(BorderStyle.THIN);
        headerStyle.setLeftBorderColor(IndexedColors.BLACK.getIndex());
        headerStyle.setBorderTop(BorderStyle.THIN);
        headerStyle.setTopBorderColor(IndexedColors.BLACK.getIndex());
        headerStyle.setBorderBottom(BorderStyle.THIN);
        headerStyle.setBottomBorderColor(IndexedColors.BLACK.getIndex());
        // 背景颜色
        headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        headerStyle.setFillForegroundColor(IndexedColors.GREY_80_PERCENT.getIndex());
        Font font = wb.createFont();
        font.setFontHeightInPoints((short) 12);
        font.setFontName("宋体");
        font.setColor(IndexedColors.WHITE.getIndex());
        headerStyle.setFont(font);

        // 数据
        XSSFCellStyle dataStyle = wb.createCellStyle();
        dataStyle.setAlignment(HorizontalAlignment.CENTER);
        dataStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        dataStyle.setBorderRight(BorderStyle.THIN);
        dataStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());
        dataStyle.setBorderLeft(BorderStyle.THIN);
        dataStyle.setLeftBorderColor(IndexedColors.BLACK.getIndex());
        dataStyle.setBorderTop(BorderStyle.THIN);
        dataStyle.setTopBorderColor(IndexedColors.BLACK.getIndex());
        dataStyle.setBorderBottom(BorderStyle.THIN);
        dataStyle.setBottomBorderColor(IndexedColors.BLACK.getIndex());
        dataStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        // 背景颜色
        dataStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
        font = wb.createFont();
        font.setFontHeightInPoints((short) 10);
        font.setFontName("宋体");
        dataStyle.setFont(font);

        // 锁定样式
        XSSFCellStyle lockStyle = wb.createCellStyle();
        lockStyle.setLocked(true);//设置锁定

        // 未锁定样式
        XSSFCellStyle unlockStyle = wb.createCellStyle();
        unlockStyle.setLocked(false);//设置未锁定

        XSSFRow headRow = sheet.createRow(0);
        // 表头
        String[] fieldsNameList = {"学号", "姓名", "学期", "考试", "扫描批次号", "课程名称(代码)", "任课老师", "教学班", "学院", "专业", "班级", "绑定图片数量（张）", "下载成功数量（页）", "下载失败（页）"};
        for (int i = 0; i < fieldsNameList.length; i++) {
            XSSFCell cell = headRow.createCell(i);
            cell.setCellValue(fieldsNameList[i]);
            cell.setCellStyle(headerStyle);
        }

        for (int i = 1; i <= list.size(); i++) {
            PictureManageDetailResult result = list.get(i - 1);
            XSSFRow row = sheet.createRow(i);

            XSSFCell cell0 = row.createCell(0);
            cell0.setCellStyle(lockStyle);
            cell0.setCellStyle(dataStyle);
            cell0.setCellValue(result.getStudentCode());

            XSSFCell cell1 = row.createCell(1);
            cell1.setCellStyle(lockStyle);
            cell1.setCellStyle(dataStyle);
            cell1.setCellValue(result.getStudentName());

            XSSFCell cell2 = row.createCell(2);
            cell2.setCellStyle(lockStyle);
            cell2.setCellStyle(dataStyle);
            cell2.setCellValue(result.getSemesterName());

            XSSFCell cell3 = row.createCell(3);
            cell3.setCellStyle(lockStyle);
            cell3.setCellStyle(dataStyle);
            cell3.setCellValue(result.getExamName());

            XSSFCell cell4 = row.createCell(4);
            cell4.setCellStyle(lockStyle);
            cell4.setCellStyle(dataStyle);
            cell4.setCellValue(Objects.isNull(result.getBatchNo()) || "null".equals(result.getBatchNo()) ? "" : result.getBatchNo());

            XSSFCell cell5 = row.createCell(5);
            cell5.setCellStyle(lockStyle);
            cell5.setCellStyle(dataStyle);
            cell5.setCellValue(result.getCourseName() + "(" + result.getCourseCode() + ")");

            XSSFCell cell6 = row.createCell(6);
            cell6.setCellStyle(lockStyle);
            cell6.setCellStyle(dataStyle);
            cell6.setCellValue(result.getTeacher());

            XSSFCell cell7 = row.createCell(7);
            cell7.setCellStyle(lockStyle);
            cell7.setCellStyle(dataStyle);
            cell7.setCellValue(result.getTeachClass());

            XSSFCell cell8 = row.createCell(8);
            cell8.setCellStyle(lockStyle);
            cell8.setCellStyle(dataStyle);
            cell8.setCellValue(result.getCollegeName());

            XSSFCell cell9 = row.createCell(9);
            cell9.setCellStyle(lockStyle);
            cell9.setCellStyle(dataStyle);
            cell9.setCellValue(result.getMajorName());

            XSSFCell cell10 = row.createCell(10);
            cell10.setCellStyle(lockStyle);
            cell10.setCellStyle(dataStyle);
            cell10.setCellValue(result.getClassName());

            XSSFCell cell11 = row.createCell(11);
            cell11.setCellStyle(lockStyle);
            cell11.setCellStyle(dataStyle);
            cell11.setCellValue(result.getBindCount());

            XSSFCell cell12 = row.createCell(12);
            cell12.setCellStyle(lockStyle);
            cell12.setCellStyle(dataStyle);
            cell12.setCellValue(successMap.get(result.getStudentId()));

            XSSFCell cell13 = row.createCell(13);
            cell13.setCellStyle(lockStyle);
            cell13.setCellStyle(dataStyle);
            cell13.setCellValue(errorMap.get(result.getStudentId()));
        }

        for (int i = 0; i < fieldsNameList.length; i++) {
            sheet.autoSizeColumn(i);
            sheet.setColumnWidth(i, sheet.getColumnWidth(i) * 17 / 13);
        }

        // sheet添加保护，这个一定要否则光锁定还是可以编辑的
        sheet.protectSheet(SystemConstant.EXCEL_PROTECT_KEY);

        File file = new File(zipLocalRootPath, fileName);
        FileOutputStream outputStream = new FileOutputStream(file);
        wb.write(outputStream);
        outputStream.close();
    }


    public Map<String, Object> executeImportExamStudentLogic(Map<String, Object> map, StringJoiner stringJoinerSummary) {
        SysUser sysUser = (SysUser) map.get(SystemConstant.USER);
        InputStream inputStream = (InputStream) map.get("inputStream");
        Long examId = SystemConstant.convertIdToLong(String.valueOf(map.get("examId")));
        StoreTypeEnum storeType = StoreTypeEnum.valueOf(String.valueOf(map.get("storeType")));
        BasicExam basicExam = basicExamService.getById(examId);
        if (Objects.isNull(basicExam)) {
            throw ExceptionResultEnum.ERROR.exception("考试不存在");
        }
        Long semesterId = basicExam.getSemesterId();

        SystemConstant.addSummary(stringJoinerSummary, "开始解析导入文件内容");
        ExamStudentParseDto examStudentParseDto;
        try {
            // 解析excel文件内容
            examStudentParseDto = this.parseImportExamStudentData(sysUser.getSchoolId(), inputStream);
        } catch (Exception e) {
            SystemConstant.addSummary(stringJoinerSummary, "解析导入文件内容结束，解析失败。" + e.getMessage());
            throw ExceptionResultEnum.ERROR.exception(e.getMessage());
        }
        SystemConstant.addSummary(stringJoinerSummary, "解析导入文件内容结束，开始进行数据校验");

        List<ExamStudentImport> examStudentImportList = examStudentParseDto.getBasicExamStudentImportList();
        if (CollectionUtils.isEmpty(examStudentImportList)) {
            throw ExceptionResultEnum.ERROR.exception("未读取到有效数据");
        }

        // 考生管理中，当前考试下已有考生集合
        List<ExamStudent> examStudentList = examStudentService.list(new QueryWrapper<ExamStudent>().lambda().eq(ExamStudent::getExamId, examId));
        Map<String, ExamStudent> courseIdStudentCodeMap = examStudentList.stream().collect(Collectors.toMap(k -> k.getCourseCode() + SystemConstant.HYPHEN + k.getStudentCode(), Function.identity()));

        // 扫描任务管理中，当前考试下已有任务集合
        List<PaperScanTask> paperScanTaskList = paperScanTaskService.listByExamId(examId);
        Map<String, StoreTypeEnum> stringStoreTypeMap = paperScanTaskList.stream().collect(Collectors.toMap(PaperScanTask::getCourseCode, PaperScanTask::getStoreType));

        Map<String, String> courseMap = examStudentList.stream().collect(Collectors.toMap(ExamStudent::getCourseCode, ExamStudent::getCourseName, (v1, v2) -> v2));
        Map<String, List<String>> studentCodeMap = new HashMap<>();
        // 是否有错误提示
        boolean hasError = false;
        List<ExamStudent> examStudents = new ArrayList<>();
        for (ExamStudentImport examStudentImport : examStudentImportList) {
            ExamStudent examStudent = new ExamStudent();
            BeanUtils.copyProperties(examStudentImport, examStudent);
            examStudent.setExtendFields(CollectionUtils.isNotEmpty(examStudentImport.getExtendFieldList()) ? JSON.toJSONString(examStudentImport.getExtendFieldList()) : null);

            // 学号去掉空格
            examStudent.setStudentCode(StringUtils.isNotBlank(examStudentImport.getStudentCode()) ? StringUtils.deleteWhitespace(examStudentImport.getStudentCode()) : null);
            // 课程代码去掉空格
            examStudent.setCourseCode(StringUtils.isNotBlank(examStudentImport.getCourseCode()) ? StringUtils.deleteWhitespace(examStudentImport.getCourseCode()) : null);
            // 课程名称去掉空格
            examStudent.setCourseName(StringUtils.isNotBlank(examStudentImport.getCourseName()) ? StringUtils.deleteWhitespace(examStudentImport.getCourseName()) : null);
            // 考场去掉空格
            examStudent.setExamRoom(StringUtils.isNotBlank(examStudentImport.getExamRoom()) ? StringUtils.deleteWhitespace(examStudentImport.getExamRoom()) : null);
            // 班级去掉空格
            examStudent.setClassName(StringUtils.isNotBlank(examStudentImport.getClassName()) ? StringUtils.deleteWhitespace(examStudentImport.getClassName()) : null);


            StringJoiner errorMsg = new StringJoiner("；");

            //设置成绩
            if (StringUtils.isNotEmpty(examStudentImport.getScore())) {
                try {
                    Double score = Double.parseDouble(examStudentImport.getScore());
                    examStudent.setScore(score);
                } catch (NumberFormatException e) {
                    errorMsg.add("成绩必须是数字");
                }
            }
            if (StringUtils.isBlank(examStudent.getStudentCode())) {
                errorMsg.add("学号必填");
            }
            if (StringUtils.isBlank(examStudent.getStudentName())) {
                errorMsg.add("姓名必填");
            }
            if (StringUtils.isBlank(examStudent.getCourseCode())) {
                errorMsg.add("课程代码必填");
            }
            if (StringUtils.isBlank(examStudent.getCourseName())) {
                errorMsg.add("课程名称必填");
            }
            if (StringUtils.isNotBlank(examStudent.getCourseName()) && (examStudent.getCourseName().contains("/") || examStudent.getCourseName().contains("\\"))) {
                errorMsg.add("课程名称不能包含正反斜杠");
            }
            if (StoreTypeEnum.ROOM.equals(storeType) && StringUtils.isBlank(examStudent.getExamRoom())) {
                errorMsg.add("考场必填");
            }
            if (StoreTypeEnum.CLASS.equals(storeType) && StringUtils.isBlank(examStudent.getClassName())) {
                errorMsg.add("班级必填");
            }
            if(StringUtils.isNotBlank(examStudent.getClassName()) && (examStudent.getClassName().contains("/") || examStudent.getClassName().contains("\\"))) {
                errorMsg.add("班级名称不能包含正反斜杠");
            }
            if(StringUtils.isNotBlank(examStudent.getExamRoom()) && (examStudent.getExamRoom().contains("/") || examStudent.getExamRoom().contains("\\"))) {
                errorMsg.add("考场不能包含正反斜杠");
            }

            if (StringUtils.isNoneBlank(examStudent.getCourseCode(), examStudent.getCourseName())) {
                if (courseMap.containsKey(examStudent.getCourseCode())) {
                    if (!examStudent.getCourseName().equals(courseMap.get(examStudent.getCourseCode()))) {
                        errorMsg.add("课程代码[" + examStudent.getCourseCode() + "]存在多个课程名称[" + courseMap.get(examStudent.getCourseCode()) + "，" + examStudent.getCourseName() + "]");
                    }
                } else {
                    courseMap.put(examStudent.getCourseCode(), examStudent.getCourseName());
                }
            }
            // 考生在同课程下重复
            if (StringUtils.isNoneBlank(examStudent.getCourseCode(), examStudent.getStudentCode())) {
                if (studentCodeMap.containsKey(examStudent.getCourseCode())) {
                    List<String> studentCodeList = studentCodeMap.get(examStudent.getCourseCode());
                    if (CollectionUtils.isEmpty(studentCodeList)) {
                        studentCodeMap.put(examStudent.getCourseCode(), new ArrayList<>());
                    } else {
                        if (studentCodeList.contains(examStudent.getStudentCode())) {
                            errorMsg.add("学号[" + examStudent.getStudentCode() + "]在课程代码[" + examStudent.getCourseCode() + "]下已存在");
                        } else {
                            studentCodeList.add(examStudent.getStudentCode());
                            studentCodeMap.put(examStudent.getCourseCode(), studentCodeList);
                        }
                    }
                } else {
                    List<String> studentCodeList = new ArrayList<>();
                    studentCodeList.add(examStudent.getStudentCode());
                    studentCodeMap.put(examStudent.getCourseCode(), studentCodeList);
                }

                String key = examStudent.getCourseCode() + SystemConstant.HYPHEN + examStudent.getStudentCode();
                if (courseIdStudentCodeMap.containsKey(key)) {
                    ExamStudent student = courseIdStudentCodeMap.get(key);
                    // 校验存储方式
                    if (stringStoreTypeMap.containsKey(examStudent.getCourseCode())) {
                        // 已扫描，不能修改考场或班级
                        if (paperLibraryService.countByStudentId(student.getId()) > 0) {
                            StoreTypeEnum courseCodeStoreType = stringStoreTypeMap.get(examStudent.getCourseCode());
                            if (StoreTypeEnum.ROOM.equals(courseCodeStoreType) && !examStudent.getExamRoom().equals(student.getExamRoom())) {
                                errorMsg.add("考生已扫描，不能更改考场，原考场为[" + student.getExamRoom() + "]");
                            }
                            if (StoreTypeEnum.CLASS.equals(courseCodeStoreType) && !examStudent.getClassName().equals(student.getClassName())) {
                                errorMsg.add("考生已扫描，不能更改班级，原班级为[" + student.getClassName() + "]");
                            }
                        }
                    }
                    examStudent.setId(student.getId());
                }
            }

            if (errorMsg.toString().length() > 0) {
                examStudentImport.setErrorMsg(errorMsg.toString());
            } else {

                if (examStudent.getId() == null) {
                    examStudent.setId(SystemConstant.getDbUuid());
                }
                examStudent.setSchoolId(sysUser.getSchoolId());
                examStudent.setSemesterId(semesterId);
                examStudent.setExamId(examId);
                examStudent.setCreateId(sysUser.getId());
                examStudent.setCreateTime(System.currentTimeMillis());
                examStudents.add(examStudent);
            }

            if (!hasError && errorMsg.toString().length() > 0) {
                hasError = true;
            }
        }

        map.put(SystemConstant.ERROR_DATA_LIST, examStudentImportList);
        map.put(SystemConstant.DATASOURCE, examStudents);
        map.put(SystemConstant.DATA_COUNT, examStudentImportList.size());
        map.put(SystemConstant.SUCCESS_DATA_COUNT, examStudents.size());
        map.put(SystemConstant.ERROR_DATA_COUNT, examStudentImportList.size() - examStudents.size());
        map.put(SystemConstant.HAS_ERROR_DATA, hasError);
        map.put(SystemConstant.COLUMN_NAMES, examStudentParseDto.getColumnNames());

        return map;
    }

    @Override
    public Map<String, Object> executeStudentPdfGenerateLogic(Map<String, Object> map) {
        SysUser user = (SysUser) map.get(SystemConstant.USER);
        Map<Long,Integer> successMap = new HashMap<>();
        Map<Long,Integer> errorMap = new HashMap<>();
        List<DocManageDetailResult> toBeGeneratedList;
        List<String> studentIdList = new ArrayList<>();
        String rootPath = fileStoreUtil.buildPath(UploadFileEnum.DOWNLOAD, true, String.valueOf(System.currentTimeMillis()));
        String pictureLocalPath;
        try {
            //以用户为粒度加锁
            lockService.trylock(LockType.STUDENT_PDF_GENERATE, user.getId());
            TBTask tbTask = (TBTask) map.get(SystemConstant.TASK);
            DownLoadPaperParams params = JSON.parseObject(tbTask.getRemark(), DownLoadPaperParams.class);
            String ids = params.getIds();
            if (StringUtils.isNotEmpty(ids)) {
                studentIdList = Arrays.asList(ids.split(","));
            }
            Long paperScanTaskId = params.getPaperScanTaskId();
            if (paperScanTaskId == null) {
                throw ExceptionResultEnum.ERROR.exception("扫描任务ID必传");
            }
            PaperScanTask scanTask = paperScanTaskService.getById(paperScanTaskId);
            if(Objects.isNull(scanTask)) {
                throw ExceptionResultEnum.ERROR.exception("扫描任务不存在");
            }
            BasicSchool school = basicSchoolService.getById(scanTask.getSchoolId());
            BasicExam basicExam = basicExamService.getById(scanTask.getExamId());
            BasicSemester basicSemester = basicSemesterService.getById(basicExam.getSemesterId());
            pictureLocalPath = rootPath + File.separator + school.getName();
            //查询考生数据
            if (!studentIdList.isEmpty()) {
                toBeGeneratedList = docManageMapper.listDetail(paperScanTaskId, studentIdList);
            } else { //根据查询条件
                String collegeName = params.getCollegeName();
                String majorName = params.getMajorName();
                String className = params.getClassName();
                String courseName = params.getCourseName();
                String teacher = params.getTeacher();
                String teachClass = params.getTeachClass();
                Boolean isBind = params.getIsBind() == null ? null : params.getIsBind() == 1;
                String param = params.getParam();
                Double minScore = params.getMinScore();
                Double maxScore = params.getMaxScore();
                Integer minBindCount = params.getMinBindCount();
                Integer maxBindCount = params.getMaxBindCount();
                String pictureSymbol = params.getPictureSymbol();
                Integer pictureCount = params.getPictureCount();
                toBeGeneratedList = docManageMapper.pageDetail(paperScanTaskId, collegeName, majorName, className, courseName, teacher, teachClass, isBind,
                        param, minScore, maxScore, minBindCount, maxBindCount, pictureSymbol, pictureCount);
            }
            //toBeGeneratedList = toBeGeneratedList.stream().filter(item -> StringUtils.isEmpty(item.getFilePath())).collect(Collectors.toList());

            //循环考生
            for(DocManageDetailResult result : toBeGeneratedList) {
                successMap.putIfAbsent(result.getStudentId(), 0);
                errorMap.putIfAbsent(result.getStudentId(), 0);
                List<File> fileList = new ArrayList<>();
                //创建本地目录
                String courseNameCode = String.format("%s(%s)", result.getCourseName(), result.getCourseCode());
                String downloadLocalPath = pictureLocalPath +File.separator + courseNameCode;
                File dir = new File(downloadLocalPath);
                if (!dir.exists()) {
                    dir.mkdirs();
                }

                //查询考生的试卷
                LambdaQueryWrapper<PaperLibrary> queryWrapper = new LambdaQueryWrapper<>();
                queryWrapper.eq(PaperLibrary::getStudentId, result.getStudentId());
                queryWrapper.eq(PaperLibrary::getPaperScanTaskId, paperScanTaskId);
                queryWrapper.orderByAsc(PaperLibrary::getSequence);
                List<PaperLibrary> paperLibraryList = paperLibraryService.list(queryWrapper);

                //下载试卷
                for(PaperLibrary paperLibrary : paperLibraryList) {
                    List<JSONObject> objectList = JSON.parseArray(paperLibrary.getPath(), JSONObject.class);
                    for (JSONObject jsonObject : objectList) {
                        try {
                            File file = paperLibraryCommonService.downloadFile(downloadLocalPath, jsonObject.getString(SystemConstant.PATH),
                                    jsonObject.getString(SystemConstant.UPLOAD_TYPE), jsonObject.getString(SystemConstant.TYPE));
                            fileList.add(file);
                            successMap.put(result.getStudentId(), successMap.get(result.getStudentId()) + 1);
                        } catch (Exception e) {
                            //下载失败
                            errorMap.put(result.getStudentId(), errorMap.get(result.getStudentId()) + 1);
                        }
                    }
                }

                //试卷写入到pdf
                if(!fileList.isEmpty()) {
                    ExamStudent student = examStudentService.getById(result.getStudentId());
                    String pdfPath = generateStudentPaperPdf(scanTask,student, result.getStudentCode(), fileList);
                    //更新考生
                    student.setFilePath(pdfPath);
                    examStudentService.updateById(student);
                }
            }

            map.put("size", successMap.size());
            //执行情况写入到excel
            String path = fileStoreUtil.buildPath(UploadFileEnum.PDF, false, school.getName(), basicSemester.getName(),basicExam.getName());
            File file = new File(path);
            if(!file.exists()) {
                boolean dirFlag = file.mkdirs();
                if(!dirFlag) {
                    log.error("[pdf生成] excel存放目录 创建失败:{}",path);
                }
            }
            createPdfExcel(path, toBeGeneratedList, successMap, errorMap);
        } catch (Exception e) {
            throw ExceptionResultEnum.ERROR.exception(e.getMessage());
        } finally {
            if (Objects.nonNull(rootPath)) {
                ConvertUtil.delFolder(rootPath);
            }
            lockService.unlock(LockType.STUDENT_PDF_GENERATE,user.getId());
        }
        return map;
    }

    private String generateStudentPaperPdf(PaperScanTask paperScanTask, ExamStudent student, String pdfName, List<File> fileList) {
        String pdfRootPath = getPdfRootPath(paperScanTask, student);
        File dir = new File(pdfRootPath);
        if (!dir.exists()) {
            boolean dirFlag = dir.mkdirs();
            if (!dirFlag) {
                log.error("[pdf生成] 目录创建失败:{}", pdfRootPath);
            }
        }

        //文件的完整路径
        String pdfPath = pdfRootPath + File.separator + pdfName + SystemConstant.PDF_SUFFIX;

        Document document = null;
        try {
            ItextDocumentInfo pageModel = new ItextDocumentInfo();
            pageModel.rotate();
            File file = new File(pdfPath);
            document = pageModel.prepareDocument(file);
            for (File imageFile : fileList) {
                Image image = new Image(ImageDataFactory.create(FileUtil.fileConvertToByteArray(imageFile)));
                document.add(image);
            }
        } catch (Exception e) {
            throw ExceptionResultEnum.ERROR.exception(e.getMessage());
        } finally {
            ItextPdfUtil.closePdf(document);
        }
        return pdfPath;
    }

    private String getPdfRootPath(PaperScanTask paperScanTask, ExamStudent examStudent) {
        BasicSchool basicSchool = basicSchoolService.getById(paperScanTask.getSchoolId());
        BasicExam basicExam = basicExamService.getById(paperScanTask.getExamId());
        BasicSemester basicSemester = basicSemesterService.getById(basicExam.getSemesterId());
        String courseNameCode = String.format("%s(%s)", paperScanTask.getCourseName(), paperScanTask.getCourseCode());
        String rootPath = fileStoreUtil.buildPath(UploadFileEnum.PDF, false, basicSchool.getName(), basicSemester.getName(), basicExam.getName(), courseNameCode);
        if (StoreTypeEnum.ROOM.equals(paperScanTask.getStoreType())) {
            rootPath = fileStoreUtil.buildPath(UploadFileEnum.PDF, false, basicSchool.getName(), basicSemester.getName(), basicExam.getName(), courseNameCode, examStudent.getExamRoom());
        } else if (StoreTypeEnum.CLASS.equals(paperScanTask.getStoreType())) {
            rootPath = fileStoreUtil.buildPath(UploadFileEnum.PDF, false, basicSchool.getName(), basicSemester.getName(), basicExam.getName(), courseNameCode, examStudent.getClassName());
        } else if (StoreTypeEnum.COURSE.equals(paperScanTask.getStoreType())) {
            rootPath = fileStoreUtil.buildPath(UploadFileEnum.PDF, false, basicSchool.getName(), basicSemester.getName(), basicExam.getName(), courseNameCode, examStudent.getStudentCode());
        }
        return rootPath;
    }

    private void createPdfExcel(String path, List<DocManageDetailResult> list, Map<Long, Integer> successMap,
            Map<Long, Integer> errorMap) throws IOException {
        String fileName = "执行清单-" + System.currentTimeMillis() +".xlsx";
        XSSFWorkbook wb = new XSSFWorkbook();
        XSSFSheet sheet = wb.createSheet("数据");

        // 表头
        CellStyle headerStyle = wb.createCellStyle();
        headerStyle.setAlignment(HorizontalAlignment.CENTER);
        headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        headerStyle.setBorderRight(BorderStyle.THIN);
        headerStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());
        headerStyle.setBorderLeft(BorderStyle.THIN);
        headerStyle.setLeftBorderColor(IndexedColors.BLACK.getIndex());
        headerStyle.setBorderTop(BorderStyle.THIN);
        headerStyle.setTopBorderColor(IndexedColors.BLACK.getIndex());
        headerStyle.setBorderBottom(BorderStyle.THIN);
        headerStyle.setBottomBorderColor(IndexedColors.BLACK.getIndex());
        // 背景颜色
        headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        headerStyle.setFillForegroundColor(IndexedColors.GREY_80_PERCENT.getIndex());
        Font font = wb.createFont();
        font.setFontHeightInPoints((short) 12);
        font.setFontName("宋体");
        font.setColor(IndexedColors.WHITE.getIndex());
        headerStyle.setFont(font);

        // 数据
        XSSFCellStyle dataStyle = wb.createCellStyle();
        dataStyle.setAlignment(HorizontalAlignment.CENTER);
        dataStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        dataStyle.setBorderRight(BorderStyle.THIN);
        dataStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());
        dataStyle.setBorderLeft(BorderStyle.THIN);
        dataStyle.setLeftBorderColor(IndexedColors.BLACK.getIndex());
        dataStyle.setBorderTop(BorderStyle.THIN);
        dataStyle.setTopBorderColor(IndexedColors.BLACK.getIndex());
        dataStyle.setBorderBottom(BorderStyle.THIN);
        dataStyle.setBottomBorderColor(IndexedColors.BLACK.getIndex());
        dataStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        // 背景颜色
        dataStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
        font = wb.createFont();
        font.setFontHeightInPoints((short) 10);
        font.setFontName("宋体");
        dataStyle.setFont(font);

        // 锁定样式
        XSSFCellStyle lockStyle = wb.createCellStyle();
        lockStyle.setLocked(true);//设置锁定

        // 未锁定样式
        XSSFCellStyle unlockStyle = wb.createCellStyle();
        unlockStyle.setLocked(false);//设置未锁定

        XSSFRow headRow = sheet.createRow(0);
        // 表头
        String[] fieldsNameList = {"学号", "姓名", "课程名称(代码)",  "是否成功", "成功图片(页)", "失败图片（页）"};
        for (int i = 0; i < fieldsNameList.length; i++) {
            XSSFCell cell = headRow.createCell(i);
            cell.setCellValue(fieldsNameList[i]);
            cell.setCellStyle(headerStyle);
        }

        for (int i = 1; i <= list.size(); i++) {
            DocManageDetailResult result = list.get(i - 1);
            XSSFRow row = sheet.createRow(i);

            XSSFCell cell0 = row.createCell(0);
            cell0.setCellStyle(lockStyle);
            cell0.setCellStyle(dataStyle);
            cell0.setCellValue(result.getStudentCode());

            XSSFCell cell1 = row.createCell(1);
            cell1.setCellStyle(lockStyle);
            cell1.setCellStyle(dataStyle);
            cell1.setCellValue(result.getStudentName());

            XSSFCell cell2 = row.createCell(2);
            cell2.setCellStyle(lockStyle);
            cell2.setCellStyle(dataStyle);
            cell2.setCellValue(result.getCourseName() + "(" + result.getCourseCode() + ")");


            XSSFCell cell3 = row.createCell(3);
            cell3.setCellStyle(lockStyle);
            cell3.setCellStyle(dataStyle);
            String remark = "";
            if (successMap.get(result.getStudentId()) > 0 && errorMap.get(result.getStudentId()) == 0) {
                remark = "成功";
            }
            if (successMap.get(result.getStudentId()) == 0 && errorMap.get(result.getStudentId()) > 0) {
                remark = "失败";
            }
            if (successMap.get(result.getStudentId()) > 0 && errorMap.get(result.getStudentId()) > 0) {
                remark = "部分成功";
            }
            cell3.setCellValue(remark);

            XSSFCell cell4 = row.createCell(4);
            cell4.setCellStyle(lockStyle);
            cell4.setCellStyle(dataStyle);
            cell4.setCellValue(successMap.get(result.getStudentId()));

            XSSFCell cell5 = row.createCell(5);
            cell5.setCellStyle(lockStyle);
            cell5.setCellStyle(dataStyle);
            cell5.setCellValue(errorMap.get(result.getStudentId()));
        }

        for (int i = 0; i < fieldsNameList.length; i++) {
            sheet.autoSizeColumn(i);
            sheet.setColumnWidth(i, sheet.getColumnWidth(i) * 17 / 6);
        }

        // sheet添加保护，这个一定要否则光锁定还是可以编辑的
        sheet.protectSheet(SystemConstant.EXCEL_PROTECT_KEY);

        File file = new File(path, fileName);
        FileOutputStream outputStream = new FileOutputStream(file);
        wb.write(outputStream);
        outputStream.close();
    }

    /**
     * 生成清单
     *
     * @param zipLocalRootPath 路径
     * @param list             数据
     * @param successMap
     * @param errorMap
     */
    public void createPaperDownloadExcel(String zipLocalRootPath, List<DocManageDetailResult> list, Map<Long, Integer> successMap, Map<Long, Integer> errorMap) throws IOException {
        String fileName = "下载清单.xlsx";
        XSSFWorkbook wb = new XSSFWorkbook();
        XSSFSheet sheet = wb.createSheet("数据");

        // 表头
        CellStyle headerStyle = wb.createCellStyle();
        headerStyle.setAlignment(HorizontalAlignment.CENTER);
        headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        headerStyle.setBorderRight(BorderStyle.THIN);
        headerStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());
        headerStyle.setBorderLeft(BorderStyle.THIN);
        headerStyle.setLeftBorderColor(IndexedColors.BLACK.getIndex());
        headerStyle.setBorderTop(BorderStyle.THIN);
        headerStyle.setTopBorderColor(IndexedColors.BLACK.getIndex());
        headerStyle.setBorderBottom(BorderStyle.THIN);
        headerStyle.setBottomBorderColor(IndexedColors.BLACK.getIndex());
        // 背景颜色
        headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        headerStyle.setFillForegroundColor(IndexedColors.GREY_80_PERCENT.getIndex());
        Font font = wb.createFont();
        font.setFontHeightInPoints((short) 12);
        font.setFontName("宋体");
        font.setColor(IndexedColors.WHITE.getIndex());
        headerStyle.setFont(font);

        // 数据
        XSSFCellStyle dataStyle = wb.createCellStyle();
        dataStyle.setAlignment(HorizontalAlignment.CENTER);
        dataStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        dataStyle.setBorderRight(BorderStyle.THIN);
        dataStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());
        dataStyle.setBorderLeft(BorderStyle.THIN);
        dataStyle.setLeftBorderColor(IndexedColors.BLACK.getIndex());
        dataStyle.setBorderTop(BorderStyle.THIN);
        dataStyle.setTopBorderColor(IndexedColors.BLACK.getIndex());
        dataStyle.setBorderBottom(BorderStyle.THIN);
        dataStyle.setBottomBorderColor(IndexedColors.BLACK.getIndex());
        dataStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        // 背景颜色
        dataStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
        font = wb.createFont();
        font.setFontHeightInPoints((short) 10);
        font.setFontName("宋体");
        dataStyle.setFont(font);

        // 锁定样式
        XSSFCellStyle lockStyle = wb.createCellStyle();
        lockStyle.setLocked(true);//设置锁定

        // 未锁定样式
        XSSFCellStyle unlockStyle = wb.createCellStyle();
        unlockStyle.setLocked(false);//设置未锁定

        XSSFRow headRow = sheet.createRow(0);
        // 表头
        String[] fieldsNameList = {"学生姓名", "学号", "专业", "班级", "课程", "任课老师", "教学班", "绑定图片数量（张）", "下载成功数量（页）", "下载失败（页）"};
        for (int i = 0; i < fieldsNameList.length; i++) {
            XSSFCell cell = headRow.createCell(i);
            cell.setCellValue(fieldsNameList[i]);
            cell.setCellStyle(headerStyle);
        }

        for (int i = 1; i <= list.size(); i++) {
            DocManageDetailResult docManageDetailResult = list.get(i - 1);
            XSSFRow row = sheet.createRow(i);

            XSSFCell cell0 = row.createCell(0);
            cell0.setCellStyle(lockStyle);
            cell0.setCellStyle(dataStyle);
            cell0.setCellValue(docManageDetailResult.getStudentName());

            XSSFCell cell1 = row.createCell(1);
            cell1.setCellStyle(lockStyle);
            cell1.setCellStyle(dataStyle);
            cell1.setCellValue(docManageDetailResult.getStudentCode());

            XSSFCell cell2 = row.createCell(2);
            cell2.setCellStyle(lockStyle);
            cell2.setCellStyle(dataStyle);
            cell2.setCellValue(docManageDetailResult.getMajorName());

            XSSFCell cell3 = row.createCell(3);
            cell3.setCellStyle(lockStyle);
            cell3.setCellStyle(dataStyle);
            cell3.setCellValue(docManageDetailResult.getClassName());

            XSSFCell cell4 = row.createCell(4);
            cell4.setCellStyle(lockStyle);
            cell4.setCellStyle(dataStyle);
            cell4.setCellValue(docManageDetailResult.getCourseName());

            XSSFCell cell5 = row.createCell(5);
            cell5.setCellStyle(lockStyle);
            cell5.setCellStyle(dataStyle);
            cell5.setCellValue(docManageDetailResult.getTeacher());

            XSSFCell cell6 = row.createCell(6);
            cell6.setCellStyle(lockStyle);
            cell6.setCellStyle(dataStyle);
            cell6.setCellValue(docManageDetailResult.getTeachClass());

            XSSFCell cell7 = row.createCell(7);
            cell7.setCellStyle(lockStyle);
            cell7.setCellStyle(dataStyle);
            cell7.setCellValue(docManageDetailResult.getBindCount());

            XSSFCell cell8 = row.createCell(8);
            cell8.setCellStyle(lockStyle);
            cell8.setCellStyle(dataStyle);
            cell8.setCellValue(successMap.get(docManageDetailResult.getStudentId()));

            XSSFCell cell9 = row.createCell(9);
            cell9.setCellStyle(lockStyle);
            cell9.setCellStyle(dataStyle);
            cell9.setCellValue(errorMap.get(docManageDetailResult.getStudentId()));
        }

        for (int i = 0; i < fieldsNameList.length; i++) {
            sheet.autoSizeColumn(i);
            sheet.setColumnWidth(i, sheet.getColumnWidth(i) * 17 / 10);
        }

        // sheet添加保护，这个一定要否则光锁定还是可以编辑的
        sheet.protectSheet(SystemConstant.EXCEL_PROTECT_KEY);

        File file = new File(zipLocalRootPath, fileName);
        FileOutputStream fout = new FileOutputStream(file);
        wb.write(fout);
        fout.close();
    }

    private ExamStudentParseDto parseImportExamStudentData(Long schoolId, InputStream inputStream) {
        ExcelReader excelReader = ExcelReader.create(ExcelType.XLSX, inputStream, 0);
        List<DataMap> dataMapList;
        try {
            dataMapList = excelReader.getDataMapList();
        } catch (Exception e) {
            throw ExceptionResultEnum.ERROR.exception("读取excel内容失败");
        }
        ExamStudentParseDto examStudentParseDto = new ExamStudentParseDto();
        List<ExamStudentImport> list = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(dataMapList)) {
            String[] columnNames = excelReader.getColumnNames();

            List<BasicField> basicFieldList = basicFieldService.listBySchoolId(schoolId);
            // 必填字段
            Map<String, BasicField> requiredMap = basicFieldList.stream().filter(m -> m.getBasicField() && m.getEnable()).collect(Collectors.toMap(BasicField::getName, e -> e));
            // 扩展字段
            Map<String, BasicField> extendMap = basicFieldList.stream().filter(m -> !m.getBasicField() && m.getEnable()).collect(Collectors.toMap(BasicField::getName, e -> e));

            // 通用规则表头
            List<String> actualTitleList = Stream.concat(requiredMap.keySet().stream(), extendMap.keySet().stream()).collect(Collectors.toList());
            if (!CollectionUtils.isEqualCollection(Arrays.asList(columnNames), actualTitleList)) {
                throw ExceptionResultEnum.ERROR.exception("表头名称错误，正确表头为【" + String.join(",", actualTitleList) + "】");
            }

            examStudentParseDto.setColumnNames(columnNames);

            dataMapList.forEach(m -> {
                if (m.size() > 0) {
                    ExamStudentImport examStudentImport = new ExamStudentImport();
                    Class<ExamStudentImport> aClass = (Class<ExamStudentImport>) examStudentImport.getClass();
                    for (Map.Entry<String, String> entry : m.entrySet()) {
                        // 必填字段
                        if (requiredMap.containsKey(entry.getKey())) {
                            BasicField basicField = requiredMap.get(entry.getKey());
                            try {
                                Field declaredField = aClass.getDeclaredField(basicField.getCode());
                                declaredField.setAccessible(true);
                                declaredField.set(examStudentImport, StringUtils.isNotBlank(entry.getValue()) ? StringUtils.deleteWhitespace(entry.getValue()) : null);
                            } catch (NoSuchFieldException e) {
                                throw ExceptionResultEnum.ERROR.exception("未获取到表头为[" + entry.getKey() + "]的属性值");
                            } catch (IllegalAccessException e) {
                                throw ExceptionResultEnum.ERROR.exception("表头为[" + entry.getKey() + "]的值赋值失败");
                            }
                            examStudentImport.getRequiredFieldList().add(new CodeNameEnableDisabledValue(basicField.getCode(), basicField.getName(), basicField.getEnable(), basicField.getDisabled(), StringUtils.isNotBlank(entry.getValue()) ? entry.getValue() : null));
                        }
                        // 扩展字段
                        if (extendMap.containsKey(entry.getKey())) {
                            BasicField basicField = extendMap.get(entry.getKey());
                            examStudentImport.getExtendFieldList().add(new CodeNameEnableDisabledValue(basicField.getCode(), basicField.getName(), basicField.getEnable(), basicField.getDisabled(), StringUtils.isNotBlank(entry.getValue()) ? entry.getValue() : null));
                        }
                    }

                    List<CodeNameEnableDisabledValue> requiredFieldList = examStudentImport.getRequiredFieldList();
                    // 必填字段
                    if (requiredMap.keySet().size() > requiredFieldList.size()) {
                        for (String s : requiredMap.keySet()) {
                            Optional<CodeNameEnableDisabledValue> optional = examStudentImport.getRequiredFieldList().stream().filter(r -> s.equals(r.getName())).findFirst();
                            if (!optional.isPresent()) {
                                BasicField basicField = requiredMap.get(s);
                                examStudentImport.getRequiredFieldList().add(new CodeNameEnableDisabledValue(basicField.getCode(), basicField.getName(), basicField.getEnable(), basicField.getDisabled(), null));
                            }
                        }
                    }
                    List<CodeNameEnableDisabledValue> extendFieldList = examStudentImport.getExtendFieldList();
                    // 扩展字段
                    if (extendMap.keySet().size() > extendFieldList.size()) {
                        for (String s : extendMap.keySet()) {
                            Optional<CodeNameEnableDisabledValue> optional = examStudentImport.getExtendFieldList().stream().filter(r -> s.equals(r.getName())).findFirst();
                            if (!optional.isPresent()) {
                                BasicField basicField = extendMap.get(s);
                                examStudentImport.getExtendFieldList().add(new CodeNameEnableDisabledValue(basicField.getCode(), basicField.getName(), basicField.getEnable(), basicField.getDisabled(), null));
                            }
                        }
                    }
                    list.add(examStudentImport);
                }
            });
        }
        examStudentParseDto.setBasicExamStudentImportList(list);
        return examStudentParseDto;
    }
}
