# ImportCaseByZip.java

    package cloud.wconnect.finance.service.chargeback.importcase;

    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.cons.CommonFieldI18nConstant;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.exception.ParseReportException;
    import cloud.wconnect.commonlib.pojo.bo.SymbolBO;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.util.*;
    import cloud.wconnect.finance.cons.S3BucketFunctionCons;
    import cloud.wconnect.finance.cons.chargeback.ImportCaseStatusCons;
    import cloud.wconnect.finance.entity.chargeback.CbCaseImportLog;
    import cloud.wconnect.finance.pojo.bo.chargeback.CaseListBO;
    import cloud.wconnect.finance.pojo.dto.chargeback.ImportCaseDTO;
    import cloud.wconnect.finance.pojo.dto.chargeback.UploadFileKeyBO;
    import cloud.wconnect.finance.service.AmazonS3Service;
    import cloud.wconnect.finance.service.chargeback.ICbCaseImportLogService;
    import cloud.wconnect.finance.service.chargeback.ICbCaseService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Maps;
    import jakarta.annotation.Resource;
    import org.apache.commons.io.FileUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.scheduling.annotation.Async;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    import org.springframework.web.multipart.MultipartFile;

    import java.io.File;
    import java.io.IOException;
    import java.io.InputStream;
    import java.time.LocalDate;
    import java.time.ZoneId;
    import java.time.format.DateTimeFormatter;
    import java.util.*;
    import java.util.stream.Collectors;

    /**
     * 导入 case
     *
     * @author yyt
     * 2023/9/8
     */
    @Service
    @DS("finance")
    public class ImportCaseByZip implements IImportCase {

        @Resource
        private ICbCaseService cbCaseService;
        @Resource
        private ICbCaseImportLogService cbCaseImportLogService;
        @Resource
        private IParseCaseCommonService parseCaseCommonService;
        @Autowired
        private AmazonS3Service amazonS3Service;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;
        @Autowired
        private ObjectMapper objectMapper;

        @Value("${aws-s3.third-party.key.chargeback.case}")
        private String s3keyCase;

        @Value("${file-max-size.import-report.chargeback}")
        private Long importReportFileMaxSize;

        private static final int IMPORT_ZIP = 1;

        /**
         * 导入Case时的最大数量限制
         */
        private static final int IMPORT_MAX_COUNT = 50;

        /**
         * 判断支持的平台
         *
         * @param type 导入方式
         * @return boolean
         * @author yyt
         * @date 2024/8/9
         */
        @Override
        public boolean supports(Integer type) {
            return type == IMPORT_ZIP;
        }

        /**
         * 解析报告数据
         *
         * @param propertyDTO 资产
         * @param in          文件流
          * @return journal 数据
         * @author yyt
         * @date 2024/8/9
         */
        @Override
        public List<CaseListBO> parsReport(PropertyDTO propertyDTO, InputStream in) throws IOException {
            return parseCaseCommonService.parseReport(propertyDTO, in);
        }

        /**
         * 检查文件
         *
         * @param propertyDTO 资产
         * @param file        文件
         * @return 文件路径
         * @author yyt
         * @date 2024/8/9
         **/
        @Override
        public ImportCaseDTO checkFile(PropertyDTO propertyDTO, MultipartFile file) {
            // 限制文件大小
            FileCheckUtils.checkFileSize(file, importReportFileMaxSize);
            // 校验文件格式
            UploadUtils.checkFileFormat(file, Lists.newArrayList("zip"));
            // to s3
            String key = toS3(propertyDTO.getTenantSymbol(), propertyDTO.getAbbr(), file);

            File newFile;
            String zipFilePath;
            File[] files;
            try {
                newFile = UploadUtils.multiFileToFile(file);
                if (Objects.isNull(newFile)) {
                    throw new ParseReportException(BaseErrorCodeEnum.SYSTEM_ERROR.getI18nCode(),
                            BaseErrorCodeEnum.SYSTEM_ERROR.getCode(), "the multiFile to file is failure");
                }

                zipFilePath = newFile.getParent() + "/" + propertyDTO.getAbbr() + CommonFieldI18nConstant.cs + System.currentTimeMillis();
                files = unzipAndCheckExcelExist(key, zipFilePath);
            } catch (BadRequestException e){
                amazonS3Service.deleteFile(S3BucketFunctionCons.CHARGEBACK_IMPORT_RECORD, Lists.newArrayList(key));
                throw e;
            } catch (Exception e) {
                amazonS3Service.deleteFile(S3BucketFunctionCons.CHARGEBACK_IMPORT_RECORD, Lists.newArrayList(key));
                throw new ParseReportException(BaseErrorCodeEnum.SYSTEM_ERROR.getI18nCode(),
                        BaseErrorCodeEnum.SYSTEM_ERROR.getCode(), "unzip file failed");
            }

            Optional<File> first = Arrays.stream(files).filter(item -> !item.isDirectory()).findFirst();
            if (first.isEmpty()) {
                amazonS3Service.deleteFile(S3BucketFunctionCons.CHARGEBACK_IMPORT_RECORD, Lists.newArrayList(key));

                throw new BadRequestException(CommonErrorI18nConstant.NOT_FOUND,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.cs),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "the case is not found");
            }

            try (InputStream inputStream = FileUtils.openInputStream(first.get())) {
                parseCaseCommonService.checkFile(propertyDTO, inputStream);
            } catch (BadRequestException e) {
                amazonS3Service.deleteFile(S3BucketFunctionCons.CHARGEBACK_IMPORT_RECORD, Lists.newArrayList(key));
                throw e;
            } catch (Exception e) {
                amazonS3Service.deleteFile(S3BucketFunctionCons.CHARGEBACK_IMPORT_RECORD, Lists.newArrayList(key));
                throw new ParseReportException(BaseErrorCodeEnum.SYSTEM_ERROR.getI18nCode(),
                        BaseErrorCodeEnum.SYSTEM_ERROR.getCode(), "parsing file failed.", e);
            }

            for (File unZipFile : files) {
                unZipFile.delete();
            }

            return new ImportCaseDTO(key, zipFilePath);
        }

        /**
         * 上传文件至 s3
         *
         * @param tenantSymbol   租户标识
         * @param propertySymbol 资产标识
         * @param file           文件
         * @return 文件路径
         * @author yyt
         * @date 2024/8/9
         **/
        private String toS3(String tenantSymbol, String propertySymbol, MultipartFile file) {
            String key = tenantSymbol + "/" + propertySymbol + "/" + s3keyCase +
                    UploadUtils.getRandomFileName(file.getOriginalFilename());
            try {
                // 上传文件
                amazonS3Service.uploadFile(S3BucketFunctionCons.CHARGEBACK_IMPORT_RECORD, file, key);
            } catch (Exception e) {
                throw new BadRequestException(BaseErrorCodeEnum.SYSTEM_ERROR.getI18nCode(),
                        BaseErrorCodeEnum.SYSTEM_ERROR.getCode(), "Uploading to s3 failure", e);
            }
            return key;
        }

        /**
         * 解压并检查 excel 存在
         *
         * @param key     s3文件路径
         * @param zipPath 指定压缩文件路径
         * @return 解压后的文件
         * @author yyt
         * @date 2024/8/9
         **/
        private File[] unzipAndCheckExcelExist(String key, String zipPath) throws IOException {
            File file = new File(zipPath + ".zip");
            FileUtils.copyToFile(amazonS3Service.getFile(S3BucketFunctionCons.CHARGEBACK_IMPORT_RECORD, key), file);
            // 解压文件路径
            String fullPath = zipPath + "/";
            // 解压文件
            UploadUtils.unZip(file, fullPath);

            // 解压后的文件
            File unZipFile = new File(fullPath);
            File[] files = unZipFile.listFiles();
            if (files == null || files.length == 0) {
                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("file"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the file is not found");
            }

            boolean onlyOneExcelFile = true;
            for (File f : files) {
                if (!f.isDirectory()) {
                    UploadUtils.checkFileFormat(f, Lists.newArrayList("xls", "xlsx"));
                    // 验证必须只有一个Excel文件
                    if (!onlyOneExcelFile) {
                        throw new BadRequestException(CommonErrorI18nConstant.ZIP_FILE_ONLY_ONE_EXCEL,
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                                "the excel must only one");
                    }
                    onlyOneExcelFile = false;
                }
            }
            return files;
        }

        /**
         * 处理导入记录
         *
         * @param tenantBO    租户
         * @param propertyDTO 资产
         * @param fileName    文件名称
         * @param key         文件路径
         * @param userId      操作人
         * @return 导入记录
         * @author yyt
         * @date 2024/8/9
         **/
        @Override
        public CbCaseImportLog handleImportLog(SymbolBO tenantBO, PropertyDTO propertyDTO, String fileName, String key, int userId) {
            return cbCaseImportLogService.addLog(userId, propertyDTO.getId(), fileName, key);
        }

        /**
         * 上传文件至s3
         *
         * @param key  文件key
         * @param file 文件
         * @return s3上传文件信息
         * @author yyt
         * 2024/8/9
         */
        private UploadFileKeyBO toS3(String key, File file) {
            amazonS3Service.uploadFile(S3BucketFunctionCons.CHARGEBACK, file, key);
            UploadFileKeyBO fileKey = new UploadFileKeyBO();
            fileKey.setP(key);
            fileKey.setT(1);
            return fileKey;
        }

        /**
         * 处理文件
         *
         * @param importDTO importDTO
         * @author yyt
         * @date 2024/8/9
         **/
        @Async
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void handleFile(ImportCaseDTO importDTO) {
            // 初始存储S3文件key信息：Case Number-issue date,文件
            Map<String, String> fileKeyByCaseNumberAndIssueDate = Maps.newHashMapWithExpectedSize(IMPORT_MAX_COUNT);
            // 存储文件key信息
            List<UploadFileKeyBO> fileKeys = Lists.newArrayListWithExpectedSize(IMPORT_MAX_COUNT);
            List<CaseListBO> cases = Lists.newArrayListWithExpectedSize(IMPORT_MAX_COUNT);
            boolean importSuccess = true;
            try {
                parseZipFile(importDTO, fileKeyByCaseNumberAndIssueDate, fileKeys, cases);
                if (!cases.isEmpty()) {
                    // case 设置附件
                    setCaseAttachment(cases, fileKeyByCaseNumberAndIssueDate);

                    cbCaseService.handleCaseData(importDTO.getPropertyDTO(), importDTO.getUserId(), cases);
                }
            } catch (Exception e) {
                // 发生异常删除s3上传的文件
                if (!fileKeys.isEmpty()) {
                    amazonS3Service.deleteFile(S3BucketFunctionCons.CHARGEBACK, fileKeys.stream().map(UploadFileKeyBO::getP).collect(Collectors.toList()));
                }

                importSuccess = false;

                throw new ParseReportException(BaseErrorCodeEnum.SYSTEM_ERROR.getI18nCode(),
                        BaseErrorCodeEnum.SYSTEM_ERROR.getCode(), "parsing file failed.", e);
            } finally {
                if (importSuccess) {
                    importSuccess(importDTO.getImportLog());
                } else {
                    importFail(importDTO.getImportLog());
                }
            }
        }

        /**
         * 解析 zip 文件
         *
         * @param importDTO                       importDTO
         * @param fileKeyByCaseNumberAndIssueDate 存储S3文件key信息
         * @param fileKeys                        s3 key BO
         * @param cases                           case
         * @author yyt
         * @date 2024/8/9
         **/
        private void parseZipFile(ImportCaseDTO importDTO, Map<String, String> fileKeyByCaseNumberAndIssueDate,
                                  List<UploadFileKeyBO> fileKeys, List<CaseListBO> cases) throws Exception {
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(BaseConstant.DATE_FORMAT_YYYYMMDD, Locale.ENGLISH);
            DateTimeFormatter dateTimeFormatter2 = DateTimeFormatter.ofPattern(BaseConstant.DATE_FORMAT_MM_DD_YYYY, Locale.ENGLISH);
            String tenantSymbol = importDTO.getTenantBO().getSymbol();
            PropertyDTO propertyDTO = importDTO.getPropertyDTO();
            String abbr = propertyDTO.getAbbr();
            String timeZone = propertyDTO.getTimezone();

            File[] files = unzipAndCheckExcelExist(importDTO.getImportLog().getUrl(), importDTO.getZipFilePath());
            for (File file : files) {
                if (file.isDirectory()) {
                    File[] attachments = file.listFiles();
                    if (attachments == null) {
                        continue;
                    }
                    int fileLength = attachments.length;
                    if (fileLength == 0) {
                        continue;
                    }
                    // 文件夹名称格式：BA-CB80191306120118BA-20200718
                    String[] fileNames = file.getName().split("-");
                    String caseNumber = fileNames[1];
                    String dateStr = dateTimeFormatter2.format(LocalDate.parse(fileNames[2], dateTimeFormatter).atStartOfDay(ZoneId.of(timeZone)));
                    List<UploadFileKeyBO> singleCaseKeys = Lists.newArrayListWithExpectedSize(fileLength);
                    for (File attachment : attachments) {
                        // key 添加文件名称
                        String key = tenantSymbol + "/" + abbr + "/" + s3keyCase + fileNames[0] + "/" + caseNumber + "/" + dateStr
                                + "/accountant/" + attachment.getName();
                        // 上传文件及存放Key
                        singleCaseKeys.add(toS3(key, attachment));
                    }
                    fileKeys.addAll(singleCaseKeys);
                    fileKeyByCaseNumberAndIssueDate.put(caseNumber + "-" + fileNames[2],
                            JsonUtils.convertObjListToString(objectMapper, singleCaseKeys));
                } else {
                    // 解析 excel 文件
                    try (InputStream in = FileUtils.openInputStream(file)) {
                        cases.addAll(parsReport(propertyDTO, in));
                    }
                }
            }

            for (File unZipFile : files) {
                unZipFile.delete();
            }
        }

        /**
         * case 设置附件
         *
         * @param cases                           case
         * @param fileKeyByCaseNumberAndIssueDate case 附件
         * @author yyt
         * @date 2024/8/9
         */
        private void setCaseAttachment(List<CaseListBO> cases, Map<String, String> fileKeyByCaseNumberAndIssueDate) {
            if (fileKeyByCaseNumberAndIssueDate.isEmpty()) {
                return;
            }
            cases.forEach(item -> {
                String caseNumberAndIssueDate = item.getCaseNumber() + "-" + item.getIssueDate().replaceAll("/", "");
                String attachments = fileKeyByCaseNumberAndIssueDate.get(caseNumberAndIssueDate);
                if (attachments != null) {
                    item.setAttachments(attachments);
                }
            });
        }

        /**
         * 导入成功
         *
         * @param importLog 导入记录
         * @author yyt
         * @date 2024/8/9
         **/
        private void importSuccess(CbCaseImportLog importLog) {
            TransactionUtils.afterCommitSyncExecute(() -> {
                // 更新导入记录
                CbCaseImportLog updateLog = new CbCaseImportLog();
                updateLog.setId(importLog.getId());
                updateLog.setStatus(ImportCaseStatusCons.IMPORT_SUCCESS);
                cbCaseImportLogService.updateById(updateLog);
            });
        }

        /**
         * 导入失败
         *
         * @param importLog 导入记录
         * @author yyt
         * @date 2024/8/9
         **/
        private void importFail(CbCaseImportLog importLog) {
            TransactionUtils.afterCompletionRollbackSyncExecute(() -> {
                // 更新导入记录
                CbCaseImportLog updateLog = new CbCaseImportLog();
                updateLog.setId(importLog.getId());
                updateLog.setStatus(ImportCaseStatusCons.IMPORT_FAIL);
                cbCaseImportLogService.updateById(updateLog);
            });
        }

    }