diff --git a/youchain-system/src/main/java/com/youchain/utils/FastExcelCellListener.java b/youchain-system/src/main/java/com/youchain/utils/FastExcelCellListener.java new file mode 100644 index 0000000..89647a1 --- /dev/null +++ b/youchain-system/src/main/java/com/youchain/utils/FastExcelCellListener.java @@ -0,0 +1,115 @@ +package com.youchain.utils; + +import cn.idev.excel.context.AnalysisContext; +import cn.idev.excel.read.listener.ReadListener; +import com.youchain.exception.BadRequestException; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public class FastExcelCellListener implements ReadListener> { + private final Map cellValues = new ConcurrentHashMap<>(); + private final Set targetCells = new HashSet<>(); + private volatile boolean allCellsRead = false; + + /** + * 添加目标单元格 + * + * @param row 行索引(从1开始,Excel格式) + * @param col 列索引(从1开始,Excel格式) + * @return 当前监听器实例,支持链式调用 + */ + public FastExcelCellListener addTargetCell(int row, int col) { + if (row < 1 || col < 1) { + throw new BadRequestException("行列索引必须从1开始"); + } + targetCells.add(new CellPosition(row, col)); + return this; + } + + @Override + public void invoke(Map rowData, AnalysisContext context) { + // 如果所有目标单元格已读取完毕,直接返回 + if (allCellsRead) { + return; + } + // EasyExcel 的行索引从0开始,转换为Excel格式(从1开始) + int currentRowIndex = context.readRowHolder().getRowIndex() + 1; + + // 检查当前行是否包含目标单元格 + for (CellPosition targetCell : targetCells) { + if (currentRowIndex == targetCell.row) { + // 列索引需要减1转换为0-based + String cellValue = rowData.get(targetCell.col - 1); + if (cellValue != null) { + cellValues.put(targetCell, cellValue); + } + } + } + + // 检查是否所有目标单元格都已读取 + if (cellValues.size() == targetCells.size()) { + allCellsRead = true; + } + } + + @Override + public void doAfterAllAnalysed(AnalysisContext analysisContext) { + // 可根据需要添加逻辑 + } + + /** + * 获取指定单元格的值 + * + * @param row 行索引(从1开始,Excel格式) + * @param col 列索引(从1开始,Excel格式) + * @return 单元格值,如果不存在返回null + */ + public String getCellValue(int row, int col) { + return cellValues.get(new CellPosition(row, col)); + } + + /** + * 获取所有单元格值(字符串格式的key) + * + * @return 所有单元格值的Map,key为"row-col"格式 + */ + public Map getAllCellValues() { + Map result = new HashMap<>(); + cellValues.forEach((pos, value) -> + result.put(pos.row + "-" + pos.col, value)); + return result; + } + + /** + * 单元格位置封装类 + */ + public static class CellPosition { + private final int row; + private final int col; + + public CellPosition(int row, int col) { + this.row = row; + this.col = col; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CellPosition that = (CellPosition) o; + return row == that.row && col == that.col; + } + + @Override + public int hashCode() { + return Objects.hash(row, col); + } + + @Override + public String toString() { + return "(" + row + "," + col + ")"; + } + } + +} diff --git a/youchain-system/src/main/java/com/youchain/utils/FastExcelUtils.java b/youchain-system/src/main/java/com/youchain/utils/FastExcelUtils.java new file mode 100644 index 0000000..3c05daa --- /dev/null +++ b/youchain-system/src/main/java/com/youchain/utils/FastExcelUtils.java @@ -0,0 +1,167 @@ +package com.youchain.utils; + +import cn.idev.excel.FastExcel; +import cn.idev.excel.write.metadata.style.WriteCellStyle; +import cn.idev.excel.write.metadata.style.WriteFont; +import cn.idev.excel.write.style.HorizontalCellStyleStrategy; +import com.youchain.exception.BadRequestException; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.MediaTypeFactory; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static cn.hutool.core.util.CharsetUtil.UTF_8; + +@Slf4j +public class FastExcelUtils { + + /** + * 设置下载文件消息头 + * + * @param response 响应 + * @param fileName 文件名 + * @param fileSize 文件大小 + */ + public static void setDownloadFileHeader(HttpServletResponse response, String fileName, Long fileSize) { + response.setCharacterEncoding(UTF_8); + if (fileSize != null) { + response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(fileSize)); + } + + if (StringUtils.isNotEmpty(fileName)) { + response.setHeader(HttpHeaders.CONTENT_TYPE, MediaTypeFactory.getMediaType(fileName).orElse(MediaType.APPLICATION_OCTET_STREAM) + ";charset=utf-8"); + try { + response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + URLEncoder.encode(fileName, UTF_8).replaceAll("\\+", "%20")); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.CONTENT_DISPOSITION); + } + } + + /** + * 获取样式 + * + * @return horizontalCellStyleStrategy + */ + public static HorizontalCellStyleStrategy getHorizontalCellStyleStrategy() { + + WriteCellStyle headWriteCellStyle = new WriteCellStyle(); + headWriteCellStyle.setFillForegroundColor(IndexedColors.SKY_BLUE.getIndex()); + + WriteFont headWriteFont = new WriteFont(); + headWriteFont.setFontName("宋体"); + headWriteFont.setColor(IndexedColors.WHITE.getIndex()); + headWriteCellStyle.setWriteFont(headWriteFont); + + WriteCellStyle contentWriteCellStyle = new WriteCellStyle(); + contentWriteCellStyle.setFillForegroundColor(IndexedColors.BLACK.getIndex()); + + return new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle); + } + + /** + * 通用单sheet导出 + */ + public static void exportExcel(HttpServletResponse response, String fileName, String sheetName, Class head, Collection data) throws IOException { + // 设置下载消息头 + setDownloadFileHeader(response, fileName, null); + //样式 + HorizontalCellStyleStrategy horizontalCellStyleStrategy = getHorizontalCellStyleStrategy(); + // 下载 + FastExcel.write(response.getOutputStream(), head) + .autoCloseStream(Boolean.FALSE) + .sheet(sheetName) + .registerWriteHandler(horizontalCellStyleStrategy) + .doWrite(data); + } + + /** + * 从 Excel 文件中读取指定类型的数据 + * + * @param file 上传的 Excel 文件 + * @param clazz 数据映射的类(如 Template1.class) + * @param 数据类型 + * @return 解析后的数据列表 + */ + public static List readExcelData(MultipartFile file, Class clazz, int sheetNo, int headRowNumber) { + List dataList; + try { + dataList = FastExcel.read(file.getInputStream()).head(clazz) + .sheet(sheetNo) + .headRowNumber(headRowNumber) + .doReadSync(); + } catch (IOException e) { + throw new BadRequestException("数据格式存在问题,无法读取"); + } + if (CollectionUtils.isEmpty(dataList)) { + throw new BadRequestException("数据为空"); + } + return dataList; + } + + + /** + * 读取Excel文件中指定位置的单元格值 + * + * @param file Excel文件 + * @param reader 指定行和列 + */ + public static Map readExcelCellValues(MultipartFile file, FastExcelCellListener reader) { + try { + FastExcel.read(file.getInputStream(), reader) + .sheet() + .doReadSync(); + } catch (IOException e) { + throw new BadRequestException("Excel文件读取失败,请检查文件格式是否正确"); + } + return reader.getAllCellValues(); + } + + /** + * 读取 Excel 文件头部的所有内容(第一行) + * + * @param file Excel 文件 + * @return 回返指定目录指定行返回的内容 + */ + public static List readHeadContent(MultipartFile file, int sheetNo, int headRowNumber) { + List data; + try { + data = FastExcel.read(file.getInputStream()) + .sheet(sheetNo) + .headRowNumber(headRowNumber) + .doReadSync(); + } catch (IOException e) { + throw new BadRequestException("数据格式存在问题,无法读取"); + } + + // 检查是否有数据 + if (CollectionUtils.isEmpty(data)) { + throw new BadRequestException("数据为空"); + } + + Object firstRow = data.get(0); + // 将头部内容转换为字符串列表 + if (firstRow instanceof Map) { + Map rowMap = (Map) firstRow; + return rowMap.values().stream() + .map(value -> value == null ? "" : value.toString()) + .collect(Collectors.toList()); + } else { + throw new BadRequestException("读取Excel头部内容格式异常"); + } + } +}