EasyExcel解析动态表头及导出

这篇具有很好参考价值的文章主要介绍了EasyExcel解析动态表头及导出。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

excel文件导入及导出,是日常开发中经常遇到的需求。本次笔者以EasyExcel为例,针对在项目中遇到的动态表头解析及导出的场景,详细介绍具体的代码实现过程。

参考地址

https://github.com/alibaba/easyexcel

前端下载

  const download = () => {
    axios({
      method: 'GET',
      url: config.http.baseUrl + '/templateDownload',
      responseType: 'blob',
    })
      .then(function (res) {
      const content = res.data
      const blob = new Blob([content], { type: "application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" })
      const downloadElement = document.createElement("a");
      const href = window.URL.createObjectURL(blob);
      downloadElement.href = href;
      downloadElement.download = decodeURI(res.headers['filename']);
      document.body.appendChild(downloadElement);
      downloadElement.click();
      document.body.removeChild(downloadElement); // 下载完成移除元素
      window.URL.revokeObjectURL(href); // 释放掉blob对象
    })
  }

模板下载

excel文件导入功能,常常需要进行模板下载,在springboot项目中,程序是以jar包的形式运行的,所以有很多小伙伴常常

遇到在本地开发中能够实现下载功能,但部署到服务器的时候,找不到模板文件的问题。

@Override
public void templateDownload(HttpServletResponse response, HttpServletRequest request) {
    //获取要下载的模板名称
    String fileName = "批量导入模板.xlsx";
    //获取文件下载路径
    String filePath = "/template/template.xlsx";
    TemplateDownloadUtil.download(response, request, fileName, filePath);
}

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;

/**
 * 模板文件下载工具类
 * @author 
 * @date 2021/05/20 9:20
 */
@Slf4j
public class TemplateDownloadUtil {

    public static void download(HttpServletResponse response, HttpServletRequest request,String fileName,String filePath){
        try {
            response.setContentType("application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
            // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
            response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
            response.setHeader("filename", URLEncoder.encode(fileName, "UTF-8"));
            response.setHeader("Access-Control-Expose-Headers", "filename,Content-Disposition");
          
            //获取文件的路径,此方式本地开发可以运行,服务器无法获取文件
//            String filePath = getClass().getResource("/template/template.xlsx").getPath();
//            FileInputStream input = new FileInputStream(filePath);
          
          	//在服务器中能够读取到模板文件
            ClassPathResource resource = new ClassPathResource(filePath);
            InputStream input = resource.getInputStream();
            OutputStream out = response.getOutputStream();
            byte[] b = new byte[2048];
            int len;
            while ((len = input.read(b)) != -1) {
                out.write(b, 0, len);
            }
            //修正 Excel在“xxx.xlsx”中发现不可读取的内容。是否恢复此工作薄的内容?如果信任此工作簿的来源,请点击"是"
//            response.setHeader("Content-Length", String.valueOf(input.getChannel().size()));
            input.close();
        } catch (Exception e) {
            log.error("下载模板失败 :", e);
        }
    }

}

EasyExcel动态表头解析

EasyExcel简单的读文件,官网中已经有详细的说明,本文不再赘述,详细操作参见

本文主要针对笔者遇到的复杂表头及动态表头进行讲解。

模板示例

EasyExcel解析动态表头及导出

解析

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 发薪单上传excel读取类
 *
 * @author yupf
 * @description Listener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
 */
@Slf4j
@Data
public class BatchReadListener extends AnalysisEventListener<Map<Integer, String>> {

    /**
     * 每隔500条存储数据库,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 500;
    //Excel数据缓存结构
    private List<Map<Integer, Map<Integer, String>>> list = new ArrayList<>();
    //Excel表头(列名)数据缓存结构
    private Map<Integer, String> headTitleMap = new HashMap<>();


    /**
     * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
     */
    private DbFileBatchService dbFileBatchService;
    private DbFileContentService dbFileContentService;
    private FileBatch fileBatch;
    private int total = 0;

    /**
     * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
     */
    public BatchReadListener(DbFileBatchService dbFileBatchService, DbFileContentService dbFileContentService, FileBatch fileBatch) {
        this.dbFileBatchService = dbFileBatchService;
        this.dbFileContentService = dbFileContentService;
        this.fileBatch = fileBatch;
    }

    /**
     * 这个每一条数据解析都会来调用
     *
     * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(Map<Integer, String> data, AnalysisContext context) {
        log.info("解析到一条数据:{}", JSON.toJSONString(data));
        total++;
        Map<Integer, Map<Integer, String>> map = new HashMap<>();
        map.put(context.readRowHolder().getRowIndex(), data);
        list.add(map);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (list.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            list.clear();
        }
    }

    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData();
        log.info("所有数据解析完成!");
    }

    /**
     * 解析表头数据
     **/
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        log.info("表头数据:{}", JSONObject.toJSONString(headMap));
        headTitleMap = headMap;
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
        log.info("{}条数据,开始存储数据库!", list.size());
        FileContent fileContent = null;
        List<FileContent> fileContentList = list.stream().flatMap(
            integerMap -> integerMap.entrySet().stream().map(entrySet -> {
                //entrySet.getKey()获取的是内容的RowIndex,实际的行数需要根据表头数进行处理
                Integer rowIndex = entrySet.getKey();
                Map<Integer, String> value = entrySet.getValue();
                log.info(JSONObject.toJSONString(value));
                fileContent = new FileContent();
                fileContent.setBatchId(fileBatch.getId());
                fileContent.setBatchNo(fileBatch.getBatchNo());
                //固定字段入库
                fileContent.setName(value.get(0) != null ? value.get(0).trim() : "");
                fileContent.setCertNo(value.get(1) != null ? value.get(1).trim() : "");
                fileContent.setRealAmount(value.get(2) != null ? value.get(2).trim() : "");
                //所有动态表头数据转为JSON串入库
                fileContent.setFieldsValue(JSONObject.toJSONString(value));
                //取实际的内容rowIndex
                fileContent.setRowNum(rowIndex + 1);
                fileContent.setCreateTime(LocalDateTime.now());
                return xcSalaryFileContent;
        	}
        )).collect(Collectors.toList());
        log.info(JSONObject.toJSONString(fileContentList));
        dbFileContentService.saveBatch(fileContentList);
        log.info("存储数据库成功!");
    }

}

    BatchReadListener listener = new BatchReadListener(dbFileBatchService, dbFileContentService, fileBatch);
    try {
        //注:headRowNumber默认为1,现赋值为2,即从第三行开始读取内容
        EasyExcel.read(fileInputStream, listener).headRowNumber(2).sheet().doRead();
    } catch (Exception e) {
        log.info("EasyExcel解析文件失败,{}", e);
        throw new CustomException("文件解析失败,请重新上传");
    }
    //获取表头信息进行处理
    Map<Integer, String> headTitleMap = listener.getHeadTitleMap();
    //获取动态表头信息
    List<String> headList = headTitleMap.keySet().stream().map(key -> {
        String head = headTitleMap.get(key);
        log.info(head);
        return head == null ? "" : head.replace("*", "");
    }).collect(Collectors.toList());
    //可以对表头进行入库保存,方便后续导出

综上,动态表头即可完成解析。

EasyExcel动态表头导出

导出示例

EasyExcel解析动态表头及导出

获取动态头
     private List<List<String>> getFileHeadList( FileBatch fileBatch) {
         String head = fileBatch.getFileHead();
         List<String> headList = Arrays.asList(head.split(","));
         List<List<String>> fileHead = headList.stream().map(item -> 	concatHead(Lists.newArrayList(item))).collect(Collectors.toList());
         fileHead.add(concatHead(Lists.newArrayList("备注")));
         return fileHead;
     }
    /**
     * 填写须知
     * @param headContent
     * @return
     */
	private List<String> concatHead(List<String> headContent) {
        String remake = "填写须知:                                                                                                \n" +
                "1.系统自动识别Excel表格,表头必须含有“企业账户号”、“企业账户名”、“实发金额”;\n" +
                "2.带 “*” 为必填字段,填写后才能上传成功;\n" +
                "3.若需上传其他表头,可自行在“实发金额”后添加表头,表头最多可添加20个,表头名称请控制在8个字以内;\n" +
                "4.填写的表头内容不可超过30个字;\n" +
                "5.实发金额支持填写到2位小数;\n" +
                "6.每次导入数据不超过5000条。\n" +
                "\n" +
                "注:请勿删除填写须知,删除后将导致文件上传失败\n" +
                "\n" +
                "表头示例:";
        headContent.add(0, remake);
        return headContent;
    }
获取数据
    List<FileContent> fileContentList = dbFileContentService.list(
        Wrappers.<FileContent>lambdaQuery()
        .eq(FileContent::getBatchId, fileBatch.getId())
        .orderByAsc(FileContent::getRowNum)
    );
    List<List<Object>> contentList = fileContentList.stream().map(fileContent -> {
        List<Object> rowList = new ArrayList<>();
        String fieldsValue = fileContent.getFieldsValue();
        JSONObject contentObj = JSONObject.parseObject(fieldsValue);
        for (int columnIndex = 0 , length = headList.size(); columnIndex < length; columnIndex++) {
            Object content = contentObj.get(columnIndex);
            rowList.add(content == null ? "" : content);
        }
        rowList.add(fileContent.getCheckMessage());
        return rowList;
    }).collect(Collectors.toList());
单元格格式设置

import com.alibaba.excel.metadata.data.DataFormatData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;

import java.util.List;

/**
 * 设置表头和填充内容的样式
 */
public class CellStyleStrategy extends HorizontalCellStyleStrategy {

    private final WriteCellStyle headWriteCellStyle;
    private final WriteCellStyle contentWriteCellStyle;

    /**
     * 操作列
     */
    private final List<Integer> columnIndexes;

    public CellStyleStrategy(List<Integer> columnIndexes,WriteCellStyle headWriteCellStyle, WriteCellStyle contentWriteCellStyle) {
        this.columnIndexes = columnIndexes;
        this.headWriteCellStyle = headWriteCellStyle;
        this.contentWriteCellStyle = contentWriteCellStyle;
    }

    //设置头样式
    @Override
    protected void setHeadCellStyle( CellWriteHandlerContext context) {
        // 获取字体实例
        WriteFont headWriteFont = new WriteFont();
        headWriteFont.setFontName("宋体");
        //表头不同处理
        if (columnIndexes.get(0).equals(context.getRowIndex())) {
            headWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
            headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);
            headWriteFont.setFontHeightInPoints((short) 12);
            headWriteFont.setBold(false);
            headWriteFont.setFontName("宋体");
        }else{
            headWriteCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
            headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
            headWriteFont.setFontHeightInPoints((short) 11);
            headWriteFont.setBold(false);
            headWriteFont.setFontName("微软雅黑");
        }
        headWriteCellStyle.setWriteFont(headWriteFont);
        DataFormatData dataFormatData = new DataFormatData();
        dataFormatData.setIndex((short)49);
        headWriteCellStyle.setDataFormatData(dataFormatData);
        if (stopProcessing(context)) {
            return;
        }
        WriteCellData<?> cellData = context.getFirstCellData();
        WriteCellStyle.merge(headWriteCellStyle, cellData.getOrCreateStyle());
    }

    //设置填充数据样式
    @Override
    protected void setContentCellStyle(CellWriteHandlerContext context) {
        WriteFont contentWriteFont = new WriteFont();
        contentWriteFont.setFontName("宋体");
        contentWriteFont.setFontHeightInPoints((short) 11);
        //设置数据填充后的实线边框
        contentWriteCellStyle.setWriteFont(contentWriteFont);
        contentWriteCellStyle.setBorderLeft(BorderStyle.THIN);
        contentWriteCellStyle.setBorderTop(BorderStyle.THIN);
        contentWriteCellStyle.setBorderRight(BorderStyle.THIN);
        contentWriteCellStyle.setBorderBottom(BorderStyle.THIN);
        DataFormatData dataFormatData = new DataFormatData();
        dataFormatData.setIndex((short)49);
        contentWriteCellStyle.setDataFormatData(dataFormatData);
        contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        WriteCellData<?> cellData = context.getFirstCellData();
        WriteCellStyle.merge(contentWriteCellStyle, cellData.getOrCreateStyle());
    }
}


行高设置

import com.alibaba.excel.write.style.row.AbstractRowHeightStyleStrategy;
import org.apache.poi.ss.usermodel.Row;

/**
 * 设置表头的自动调整行高策略
 */
public class CellRowHeightStyleStrategy extends AbstractRowHeightStyleStrategy {

    @Override
    protected void setHeadColumnHeight(Row row, int relativeRowIndex) {
        //设置主标题行高为17.7
        if(relativeRowIndex == 0){
            //如果excel需要显示行高为15,那这里就要设置为15*20=300
            row.setHeight((short) 3240);
        }
    }

    @Override
    protected void setContentColumnHeight(Row row, int relativeRowIndex) {
    }
}

列宽度自适应

如果是简单表头,可以使用EasyExcel中的LongestMatchColumnWidthStyleStrategy()来实现。

EasyExcel.write(fileName, LongestMatchColumnWidthData.class)
    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet("模板").doWrite(dataLong());

如果是复杂表头,就需要自己来实现,代码如下:


import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.CellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.poi.ss.usermodel.Cell;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author yupf
 * @description
 * @date 2022/9/7 18:48
 */
@Slf4j
public class CellWidthStyleStrategy extends AbstractColumnWidthStyleStrategy {
    private Map<Integer, Map<Integer, Integer>> CACHE = new HashMap<>();

    @Override
    protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        Map<Integer, Integer> maxColumnWidthMap = CACHE.get(writeSheetHolder.getSheetNo());
        if (maxColumnWidthMap == null) {
            maxColumnWidthMap = new HashMap<>();
            CACHE.put(writeSheetHolder.getSheetNo(), maxColumnWidthMap);
        }
        if (isHead) {
            if(relativeRowIndex.intValue() == 1){
                Integer length = cell.getStringCellValue().getBytes().length;
                Integer maxColumnWidth = maxColumnWidthMap.get(cell.getColumnIndex());
                if (maxColumnWidth == null || length > maxColumnWidth) {
                    maxColumnWidthMap.put(cell.getColumnIndex(), length);
                    writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), length * 300);
                }
            }
        }else{
            Integer columnWidth = this.dataLength(cellDataList, cell, isHead);
            if (columnWidth >= 0) {
                if (columnWidth > 255) {
                    columnWidth = 255;
                }
                Integer maxColumnWidth = maxColumnWidthMap.get(cell.getColumnIndex());
                if (maxColumnWidth == null || columnWidth > maxColumnWidth) {
                    maxColumnWidthMap.put(cell.getColumnIndex(), columnWidth);
                    writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), columnWidth * 256);
                }
            }
        }
    }

    private Integer dataLength(List<WriteCellData<?>> cellDataList, Cell cell, Boolean isHead) {
        if (isHead) {
            return cell.getStringCellValue().getBytes().length;
        } else {
            CellData cellData = cellDataList.get(0);
            CellDataTypeEnum type = cellData.getType();
            if (type == null) {
                return -1;
            } else {
                switch (type) {
                    case STRING:
                        return cellData.getStringValue().getBytes().length;
                    case BOOLEAN:
                        return cellData.getBooleanValue().toString().getBytes().length;
                    case NUMBER:
                        return cellData.getNumberValue().toString().getBytes().length;
                    default:
                        return -1;
                }
            }
        }
    }
}

写入文件
EasyExcel.write(response.getOutputStream())
    .head(head)
    .registerWriteHandler(new CellRowHeightStyleStrategy())   //设置行高的策略
    .registerWriteHandler(new CellStyleStrategy(Arrays.asList(0,1),new WriteCellStyle(), new WriteCellStyle()))
    .registerWriteHandler(new CellWidthStyleStrategy())
    .sheet(sheetName)
    .doWrite(list);

总结

以上便是EasyExcel解析动态表头及导出的整个过程。

在使用过程中,笔者的感受是,上手难度很低,很适合新手去做简单的表格解析,当然,如果你的需求有复杂的格式,EasyExcel也提供了api,能够很好的满足需要。文章来源地址https://www.toymoban.com/news/detail-406683.html

到了这里,关于EasyExcel解析动态表头及导出的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java——使用EasyExcel导出动态列的Excel

    多多点赞,会变好看! 多多留言,会变有钱! 有些时候列表的列可能是动态的,需要根据动态表头导出Excel文件,这时候可以使用下面的方法解决: 静态列导出代码:

    2024年02月15日
    浏览(34)
  • EasyExcel导出Excel文件

    方法一 导入EasyExcel依赖 创建实体类 OrderServiceImpl 如果希望多个sheet导出那么可以 测试类 方法二 导入EasyExcel依赖 编写ExcelUtil 编写Service层代码 controller层代码 方法一与方法二都使用了EasyExcel进行Excel的导出,区别在于方法一建立了实体类进行Excel的导出,这样的好处是可以直

    2024年02月14日
    浏览(25)
  • EasyExcel实现Excel文件导入导出功能

    Java领域解析、生成Excel比较有名的框架有Apache poi、jxl等。但他们都存在一个严重的问题就是非常的耗内存。如果你的系统并发量不大的话可能还行,但是一旦并发上来后一定会OOM或者JVM频繁的full gc。 EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。

    2024年02月02日
    浏览(54)
  • 【JAVA】easyexcel 导出excel文件带多个图片

    最终效果  pom版本 实现代码  

    2024年02月16日
    浏览(26)
  • easyexcel根据模板导出Excel文件,表格自动填充问题

    同事在做easyexcel导出Excel,根据模板导出的时候,发现导出的表格,总会覆盖落款的内容。 这就很尴尬了,表格居然不能自动填充,直接怒喷工具,哈哈。 然后一起看了一下这个问题。 我找了自己的系统中关于表格导出的页面,导出了一下,发现可以正常扩充。 于是排查问

    2024年02月06日
    浏览(38)
  • 【Java结合EasyExcel,模板文件填充并导出Excel】

    需求描述: 客户网页上填一个Excel表格,数据存到数据库,这个导出接口要做的就是从数据库中的获取数据并填充到模板文件,最后通过response返给前端一个下载链接,用户即可获取填充好的Excel文件。 方案一: 一开始使用的是easypoi,发现当填充一行数据时是OK的,但是如果

    2024年02月09日
    浏览(40)
  • 使用easyexcel 导出多级表头demo

    先看效果: 1、引入maven依赖 2、实体类 3、web导出 controller 4、在拦截器中对表头的样式进行自定义的覆盖

    2024年01月18日
    浏览(28)
  • 使用POI和EasyExcel来实现excel文件的导入导出

    废话不多说咱们直接上干货!!!! 一.读取Excel表格 【1】使用POI读取excel表格中的数据 POI还可以操作我们这个word文档等等,他不仅仅只能弄Excel,而JXI只能操作excel 1.POI的结构,我们可以更具文件的类去选择 相关的对象我当前是使用的XLSX来操作的 HSSF - 提供读写Microsoft

    2024年02月05日
    浏览(37)
  • Node.js @zurmokeeper/exceljs 如何快速导出多表头的excel文件

    Node.js 如何快速导出嵌套列(多表头)的excel文件。效果图如下: 1:使用 @zurmokeeper/exceljs, V4.4.1以上 安装: npm i @zurmokeeper/exceljs 2: 有一个 worksheet.makeColumns 方法,API文档: 代码示例:

    2024年02月10日
    浏览(35)
  • EasyExcel复杂表头导出(一对多)升级版

            在之前写的 EasyExcel复杂表头导出(一对多)的博客的结尾,受限于当时的能力和精力,留下一些问题及展望。现在写下此博客,目的就是解决之前遗留的问题。         背景介绍,见上述链接指向的博客,这里主要通过 自定义拦截器 的形式来完美解决。 对于图

    2024年02月06日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包