package com.qingjian.module.dm.pre; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.IdUtil; import com.qingjian.common.enums.DelimiterEnum; import com.qingjian.common.enums.FileSheetStateEnum; import com.qingjian.common.model.dm.entity.FileInfo; import lombok.extern.slf4j.Slf4j; import org.apache.commons.csv.CSVFormat; import org.apache.fesod.sheet.ExcelReader; import org.apache.fesod.sheet.FesodSheet; import org.apache.fesod.sheet.cache.selector.SimpleReadCacheSelector; import org.apache.fesod.sheet.read.metadata.ReadWorkbook; import org.apache.fesod.sheet.read.metadata.holder.ReadWorkbookHolder; import org.apache.fesod.sheet.read.metadata.holder.csv.CsvReadWorkbookHolder; import org.apache.fesod.sheet.support.ExcelTypeEnum; import org.apache.poi.EncryptedDocumentException; import org.apache.poi.UnsupportedFileFormatException; import org.apache.poi.ooxml.POIXMLException; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.Objects; /** * 文件预处理任务 * 负责读取 Excel/CSV 文件,检测文件类型和编码,启动预览数据解析流程 * 支持加密文件处理和自定义分隔符配置 */ @Slf4j public class PreTask { /** * 文件路径 */ private final Path filePath; /** * 批次 ID,同一批次的文件共享相同 ID */ private final Long batchId; /** * 文件名 */ private final String fileName; /** * 文件密码,用于处理加密的 Excel 文件 */ private final String password; /** * CSV 分隔符,仅 CSV 文件使用 */ private final Character delimiter; /** * 文件内容 MD5,用于去重和落库保存 */ private final String md5; /** * 构造函数 * * @param filePath 文件路径 * @param batchId 批次 ID * @param password 文件密码 * @param delimiter CSV 分隔符,默认使用逗号 * @param md5 文件内容 MD5 */ public PreTask(Path filePath, Long batchId, String password, Character delimiter, String md5) { this.filePath = filePath; this.batchId = batchId; this.fileName = FileUtil.getName(filePath); this.password = password; this.delimiter = Objects.requireNonNullElse(delimiter, ','); this.md5 = md5; } /** * 执行文件预处理任务 * 检测文件类型和编码,解析文件结构,提取预览数据 * * @return 文件信息列表,包含主文件和所有工作表的元数据 */ public List call() { final List fileInfoList = new ArrayList<>(); final FileInfo fileInfo = new FileInfo(); // 初始化文件基本信息 initDefaultFileInfo(fileInfo); Long fileId = IdUtil.getSnowflakeNextId(); fileInfo.setId(fileId); fileInfo.setFilePath(this.filePath.toString()); fileInfo.setMd5(md5); fileInfoList.add(fileInfo); // 默认使用 UTF-8 编码 Charset charset = StandardCharsets.UTF_8; ExcelTypeEnum excelTypeEnum; try { // 读取文件流并检测文件类型 InputStream inputStream = Files.newInputStream(filePath); ReadWorkbook readWorkbook = new ReadWorkbook(); readWorkbook.setInputStream(inputStream); excelTypeEnum = ExcelTypeEnum.valueOf(readWorkbook); IoUtil.close(inputStream); // CSV 文件需要检测实际编码 fileInfo.setFileType(excelTypeEnum.name()); if (excelTypeEnum.equals(ExcelTypeEnum.CSV)) { Charset newCharset = CharsetUtil.defaultCharset(Files.newInputStream(filePath)); if (!newCharset.equals(StandardCharsets.UTF_16BE)) { charset = newCharset; } } } catch (IOException e) { log.error(e.getMessage(), e); return fileInfoList; } // 设置文件编码和密码 fileInfo.setCharSet(charset.displayName()); fileInfo.setPassword(password); fileInfo.setDelimiter(DelimiterEnum.getEnum(delimiter)); fileInfo.setFileStatus(FileSheetStateEnum.SHEET_TEMPLATE_MATCHED_FAIL); // 创建预览数据监听器并开始解析 PreDataListener preDataListener = new PreDataListener(fileInfoList, fileName, fileId, batchId); try (ExcelReader reader = FesodSheet.read(Files.newInputStream(filePath), preDataListener) .readCacheSelector(new SimpleReadCacheSelector(20L, 500)) .password(password).headRowNumber(0).excelType(excelTypeEnum) .autoTrim(true).charset(charset).registerConverter(new Str2NullConverter()) .registerConverter(new DateNumberConverter()).build()) { ReadWorkbookHolder readWorkbookHolder = reader.analysisContext().readWorkbookHolder(); // 判断只有 csv 才需要修改配置信息 if (readWorkbookHolder instanceof CsvReadWorkbookHolder csvReadWorkbookHolder) { // 底层读取 csv 用的是 apache 的 common-csv,所以设置 CsvFormat 即可 csvReadWorkbookHolder.setCsvFormat(CSVFormat.DEFAULT.builder().setDelimiter(delimiter).get()); } // 开始解析所有工作表 reader.readAll(); } catch (Exception e) { log.error(e.getMessage(), e); // 不支持的文件格式 if (e instanceof UnsupportedFileFormatException || e instanceof POIXMLException) { fileInfo.setFileStatus(FileSheetStateEnum.FILE_UNSUPPORTED_FAIL); fileInfo.setFileType("unsupported"); } // 加密文件异常 if (e instanceof EncryptedDocumentException) { fileInfo.setFileStatus(FileSheetStateEnum.FILE_ENCRYPTION); } } fileInfo.setFileStatus(FileSheetStateEnum.FILE_READ_COMPLETE); return fileInfoList; } /** * 初始化文件基本信息 * 设置默认的文件类型、状态、时间戳等 * * @param fileInfo 待初始化的文件信息对象 */ private void initDefaultFileInfo(FileInfo fileInfo) { fileInfo.setFileType(ExcelTypeEnum.XLS.name()); fileInfo.setPid(0L); fileInfo.setFileName(fileName); fileInfo.setDataNum(0); fileInfo.setSuccessNum(0); fileInfo.setFailNum(0); fileInfo.setFileSize(0L); fileInfo.setBatchId(batchId); fileInfo.setFileStatus(FileSheetStateEnum.FILE_READ_FAIL); fileInfo.setCreateDateTime(LocalDateTime.now()); } }