【Java基础】Java导Excel攻略

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
img

  • 推荐:kwan 的首页,持续学习,不断总结,共同进步,活到老学到老
  • 导航
    • 檀越剑指大厂系列:全面总结 java 核心技术点,如集合,jvm,并发编程 redis,kafka,Spring,微服务,Netty 等
    • 常用开发工具系列:罗列常用的开发工具,如 IDEA,Mac,Alfred,electerm,Git,typora,apifox 等
    • 数据库系列:详细总结了常用数据库 mysql 技术点,以及工作中遇到的 mysql 问题等
    • 懒人运维系列:总结好用的命令,解放双手不香吗?能用一个命令完成绝不用两个操作
    • 数据结构与算法系列:总结数据结构和算法,不同类型针对性训练,提升编程思维,剑指大厂

非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。💝💝💝 ✨✨ 欢迎订阅本专栏 ✨✨

博客目录

    • 一.简单介绍
      • 1.需求背景
      • 2.常见情形
    • 二.基础使用
      • 1.Apache POI
      • 2.pom 依赖
      • 3.具体实现
    • 三.EasyExcel
      • 1. EasyExcel
      • 2.相关特性
      • 3.简单示例
    • 四.进阶使用
      • 1. EasyExcelUtil
      • 2.EasyExcelWriterFactory
      • 3.ExcelUtil
      • 4.使用方式
      • 5.如何处理不确定的字段?
      • 6.需要注意的点

一.简单介绍

1.需求背景

在项目开发过程中,很多时候需要用到导出功能,将表格中的数据通过导出到 excel 的方式给到业务方核对数据,在模版不固定的情况下,如何快速的导出数据到 excel 显得尤为关键,本文将介绍导出 excel 的方式。

2.常见情形

  1. 报表生成: 在业务应用中,经常需要生成各种形式的报表,以便对数据进行汇总、分析和可视化展示。将数据导出到 Excel 文件可以让用户方便地使用 Excel 等工具进行进一步的数据处理和分析。
  2. 数据备份: 导出数据到 Excel 文件是一种常见的备份手段。用户可以定期将系统中的关键数据导出到 Excel,以便在需要时进行恢复或迁移。
  3. 数据交换: 在与其他系统或应用程序进行数据交换时,导出数据到 Excel 是一种通用的方式。Excel 文件格式是广泛支持的,易于在不同系统之间进行数据传递。
  4. 用户下载: 提供给用户下载其个人或业务数据的功能。这对于在线服务、电子商务平台等应用程序是很常见的需求,用户可以将其数据保存到本地以备查阅。
  5. 批量操作: 在某些情况下,用户可能需要对大量数据进行批量操作,例如批量更新、删除或进行其他处理。将数据导出到 Excel,用户可以在本地应用程序中更轻松地执行这些操作。
  6. 报价单、发票等业务文档: 在销售和财务领域,导出 Excel 可以用于生成报价单、发票和其他业务文档,这些文档通常需要以表格形式呈现。
  7. 数据分享: 有时,用户可能希望分享特定数据的快照或分析结果。将数据导出到 Excel 文件可以方便地与其他人共享数据。

二.基础使用

1.Apache POI

在 Java 中使用 POI 库(Apache POI)可以方便地操作 Excel 文件,包括导出数据和设置单元格的样式,其中包括背景颜色。下面是一个简单的例子,演示如何在 Java 中使用 POI 库导出带有背景颜色的 Excel 文件。

2.pom 依赖

首先,确保你的项目中包含了 Apache POI 库的依赖。如果使用 Maven,可以在 pom.xml 文件中添加以下依赖:

<dependencies>
    <!-- Apache POI -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>5.0.0</version>
    </dependency>
</dependencies>

3.具体实现

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.FileOutputStream;
import java.io.IOException;

public class ExcelExporter {

    public static void main(String[] args) {
        try {
            // 创建工作簿
            Workbook workbook = new XSSFWorkbook();
            // 创建工作表
            Sheet sheet = workbook.createSheet("Sheet1");

            // 创建样式
            CellStyle style = workbook.createCellStyle();
            style.setFillForegroundColor(IndexedColors.LIGHT_YELLOW.getIndex());
            style.setFillPattern(FillPatternType.SOLID_FOREGROUND);

            // 创建行和单元格,并设置背景颜色
            Row row = sheet.createRow(0);
            Cell cell = row.createCell(0);
            cell.setCellValue("内容");
            cell.setCellStyle(style);

            // 导出文件
            try (FileOutputStream fileOut = new FileOutputStream("workbook.xlsx")) {
                workbook.write(fileOut);
            }

            // 关闭工作簿
            workbook.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

三.EasyExcel

1. EasyExcel

上述使用的原生的 Apache POI 通用性不够强,在某些方面使用起来还是不够方便,接下来将介绍阿里的 EasyExcel 的使用,在 POI 的基础上进行了封装,方便开发者直接使用。

EasyExcel 是阿里巴巴开源的一款基于 Java 的简单、高效的 Excel 文件读写工具。它可以帮助开发者更方便地进行 Excel 文件的读写操作,支持读取大数据量的 Excel 文件并且性能较好。

2.相关特性

  1. 简单易用: EasyExcel 提供了简单的 API,易于上手,使用起来相对轻松。
  2. 高性能: EasyExcel 使用了基于注解的对象模型,采用零反射、零异常的设计,因此性能较好。在处理大量数据时,相比于一些其他库,EasyExcel 通常表现更出色。
  3. 支持读写 Excel: 提供了读写 Excel 文件的功能,可以实现从 Excel 文件中读取数据,也可以将数据写入 Excel 文件。
  4. 支持多种数据模型: 可以支持 Java 普通对象、Map、List 等多种数据模型,便于适应不同的数据结构。
  5. 支持复杂报表: 可以实现复杂报表的导入导出,包括多表头、合并单元格等。
  6. 支持自定义样式: 可以自定义 Excel 单元格样式,包括字体、颜色、边框等。
  7. 支持多种 Excel 格式: 可以读写多种 Excel 格式,包括 xls 和 xlsx。

3.简单示例

读取 Excel:

// 读取 Excel 文件
String fileName = "example.xlsx";
EasyExcel.read(fileName, UserData.class, new UserDataListener()).sheet().doRead();

写入 Excel:

// 写入 Excel 文件
String fileName = "example.xlsx";
List<UserData> data = initData(); // 初始化数据
EasyExcel.write(fileName, UserData.class).sheet("Sheet1").doWrite(data);

监听器示例:

public class UserDataListener extends AnalysisEventListener<UserData> {

    // 处理每一行的数据
    @Override
    public void invoke(UserData data, AnalysisContext context) {
        System.out.println("Read data: " + data);
    }

    // 所有数据解析完成后调用
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        System.out.println("All data parsed successfully.");
    }
}

这只是一个简单的示例,实际使用中可以根据需求进行更灵活和复杂的配置。EasyExcel 提供了更多的 API 和功能,以满足不同场景下的需求。可以通过 EasyExcel 的官方文档和示例代码深入了解其更多功能和用法:EasyExcel GitHub 仓库。

四.进阶使用

1. EasyExcelUtil

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.write.handler.WriteHandler;
import org.apache.poi.ss.formula.functions.T;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
import java.util.Set;


public class EasyExcelUtil {

    /**
     * 同步无模型读(默认读取sheet0,从第2行开始读)
     *
     * @param filePath excel文件的绝对路径
     */
    public static List<Map<Integer, String>> syncRead(String filePath) {
        return EasyExcelFactory.read(filePath).sheet().doReadSync();
    }

    /**
     * 同步无模型读(自定义读取sheetX,从第2行开始读)
     *
     * @param filePath excel文件的绝对路径
     * @param sheetNo  sheet页号,从0开始
     */
    public static List<Map<Integer, String>> syncRead(String filePath, Integer sheetNo) {
        return EasyExcelFactory.read(filePath).sheet(sheetNo).doReadSync();
    }

    /**
     * 同步无模型读(指定sheet和表头占的行数)
     *
     * @param filePath
     * @param sheetNo    sheet页号,从0开始
     * @param headRowNum 表头占的行数,从0开始(如果要连表头一起读出来则传0)
     */
    public static List<Map<Integer, String>> syncRead(String filePath, Integer sheetNo, Integer headRowNum) {
        return EasyExcelFactory.read(filePath).sheet(sheetNo).headRowNumber(headRowNum).doReadSync();
    }

    /**
     * 同步无模型读(指定sheet和表头占的行数)
     *
     * @param inputStream
     * @param sheetNo     sheet页号,从0开始
     * @param headRowNum  表头占的行数,从0开始(如果要连表头一起读出来则传0)
     */
    public static List<Map<Integer, String>> syncRead(InputStream inputStream, Integer sheetNo, Integer headRowNum) {
        return EasyExcelFactory.read(inputStream).sheet(sheetNo).headRowNumber(headRowNum).doReadSync();
    }

    /**
     * 同步无模型读(指定sheet和表头占的行数)
     *
     * @param file
     * @param sheetNo    sheet页号,从0开始
     * @param headRowNum 表头占的行数,从0开始(如果要连表头一起读出来则传0)
     */
    public static List<Map<Integer, String>> syncRead(File file, Integer sheetNo, Integer headRowNum) {
        return EasyExcelFactory.read(file).sheet(sheetNo).headRowNumber(headRowNum).doReadSync();
    }
//====================================================无JAVA模型读取excel数据===============================================================

//====================================================将excel数据同步到JAVA模型属性里===============================================================

    /**
     * 同步按模型读(默认读取sheet0,从第2行开始读)
     *
     * @param filePath
     * @param clazz    模型的类类型(excel数据会按该类型转换成对象)
     */
    public static List<T> syncReadModel(String filePath, Class clazz) {
        return EasyExcelFactory.read(filePath).sheet().head(clazz).doReadSync();
    }

    /**
     * 同步按模型读(默认表头占一行,从第2行开始读)
     *
     * @param filePath
     * @param clazz    模型的类类型(excel数据会按该类型转换成对象)
     * @param sheetNo  sheet页号,从0开始
     */
    public static List<T> syncReadModel(String filePath, Class clazz, Integer sheetNo) {
        return EasyExcelFactory.read(filePath).sheet(sheetNo).head(clazz).doReadSync();
    }

    /**
     * 同步按模型读(指定sheet和表头占的行数)
     *
     * @param inputStream
     * @param clazz       模型的类类型(excel数据会按该类型转换成对象)
     * @param sheetNo     sheet页号,从0开始
     * @param headRowNum  表头占的行数,从0开始(如果要连表头一起读出来则传0)
     */
    public static List<T> syncReadModel(InputStream inputStream, Class clazz, Integer sheetNo, Integer headRowNum) {
        return EasyExcelFactory.read(inputStream).sheet(sheetNo).headRowNumber(headRowNum).head(clazz).doReadSync();
    }

    /**
     * 同步按模型读(指定sheet和表头占的行数)
     *
     * @param file
     * @param clazz      模型的类类型(excel数据会按该类型转换成对象)
     * @param sheetNo    sheet页号,从0开始
     * @param headRowNum 表头占的行数,从0开始(如果要连表头一起读出来则传0)
     */
    public static List<T> syncReadModel(File file, Class clazz, Integer sheetNo, Integer headRowNum) {
        return EasyExcelFactory.read(file).sheet(sheetNo).headRowNumber(headRowNum).head(clazz).doReadSync();
    }

    /**
     * 同步按模型读(指定sheet和表头占的行数)
     *
     * @param filePath
     * @param clazz      模型的类类型(excel数据会按该类型转换成对象)
     * @param sheetNo    sheet页号,从0开始
     * @param headRowNum 表头占的行数,从0开始(如果要连表头一起读出来则传0)
     */
    public static List<T> syncReadModel(String filePath, Class clazz, Integer sheetNo, Integer headRowNum) {
        return EasyExcelFactory.read(filePath).sheet(sheetNo).headRowNumber(headRowNum).head(clazz).doReadSync();
    }

    /**
     * 异步无模型读(默认读取sheet0,从第2行开始读)
     *
     * @param excelListener 监听器,在监听器中可以处理行数据LinkedHashMap,表头数据,异常处理等
     * @param filePath      表头占的行数,从0开始(如果要连表头一起读出来则传0)
     */
    public static void asyncRead(String filePath, AnalysisEventListener<T> excelListener) {
        EasyExcelFactory.read(filePath, excelListener).sheet().doRead();
    }

    /**
     * 异步无模型读(默认表头占一行,从第2行开始读)
     *
     * @param filePath      表头占的行数,从0开始(如果要连表头一起读出来则传0)
     * @param excelListener 监听器,在监听器中可以处理行数据LinkedHashMap,表头数据,异常处理等
     * @param sheetNo       sheet页号,从0开始
     */
    public static void asyncRead(String filePath, AnalysisEventListener<T> excelListener, Integer sheetNo) {
        EasyExcelFactory.read(filePath, excelListener).sheet(sheetNo).doRead();
    }

    /**
     * 异步无模型读(指定sheet和表头占的行数)
     *
     * @param inputStream
     * @param excelListener 监听器,在监听器中可以处理行数据LinkedHashMap,表头数据,异常处理等
     * @param sheetNo       sheet页号,从0开始
     * @param headRowNum    表头占的行数,从0开始(如果要连表头一起读出来则传0)
     */
    public static void asyncRead(InputStream inputStream, AnalysisEventListener<T> excelListener, Integer sheetNo, Integer headRowNum) {
        EasyExcelFactory.read(inputStream, excelListener).sheet(sheetNo).headRowNumber(headRowNum).doRead();
    }

    /**
     * 异步无模型读(指定sheet和表头占的行数)
     *
     * @param file
     * @param excelListener 监听器,在监听器中可以处理行数据LinkedHashMap,表头数据,异常处理等
     * @param sheetNo       sheet页号,从0开始
     * @param headRowNum    表头占的行数,从0开始(如果要连表头一起读出来则传0)
     */
    public static void asyncRead(File file, AnalysisEventListener<T> excelListener, Integer sheetNo, Integer headRowNum) {
        EasyExcelFactory.read(file, excelListener).sheet(sheetNo).headRowNumber(headRowNum).doRead();
    }

    /**
     * 异步无模型读(指定sheet和表头占的行数)
     *
     * @param filePath
     * @param excelListener 监听器,在监听器中可以处理行数据LinkedHashMap,表头数据,异常处理等
     * @param sheetNo       sheet页号,从0开始
     * @param headRowNum    表头占的行数,从0开始(如果要连表头一起读出来则传0)
     * @return
     */
    public static void asyncRead(String filePath, AnalysisEventListener<T> excelListener, Integer sheetNo, Integer headRowNum) {
        EasyExcelFactory.read(filePath, excelListener).sheet(sheetNo).headRowNumber(headRowNum).doRead();
    }

    /**
     * 异步按模型读取(默认读取sheet0,从第2行开始读)
     *
     * @param filePath
     * @param excelListener 监听器,在监听器中可以处理行数据LinkedHashMap,表头数据,异常处理等
     * @param clazz         模型的类类型(excel数据会按该类型转换成对象)
     */
    public static void asyncReadModel(String filePath, AnalysisEventListener<T> excelListener, Class clazz) {
        EasyExcelFactory.read(filePath, clazz, excelListener).sheet().doRead();
    }

    /**
     * 异步按模型读取(默认表头占一行,从第2行开始读)
     *
     * @param filePath
     * @param excelListener 监听器,在监听器中可以处理行数据LinkedHashMap,表头数据,异常处理等
     * @param clazz         模型的类类型(excel数据会按该类型转换成对象)
     * @param sheetNo       sheet页号,从0开始
     */
    public static void asyncReadModel(String filePath, AnalysisEventListener<T> excelListener, Class clazz, Integer sheetNo) {
        EasyExcelFactory.read(filePath, clazz, excelListener).sheet(sheetNo).doRead();
    }

    /**
     * 异步按模型读取
     *
     * @param inputStream
     * @param excelListener 监听器,在监听器中可以处理行数据LinkedHashMap,表头数据,异常处理等
     * @param clazz         模型的类类型(excel数据会按该类型转换成对象)
     * @param sheetNo       sheet页号,从0开始
     * @param headRowNum    表头占的行数,从0开始(如果要连表头一起读出来则传0)
     */
    public static void asyncReadModel(InputStream inputStream, AnalysisEventListener<T> excelListener, Class clazz, Integer sheetNo, Integer headRowNum) {
        EasyExcelFactory.read(inputStream, clazz, excelListener).sheet(sheetNo).headRowNumber(headRowNum).doRead();
    }

    /**
     * 异步按模型读取
     *
     * @param file
     * @param excelListener 监听器,在监听器中可以处理行数据LinkedHashMap,表头数据,异常处理等
     * @param clazz         模型的类类型(excel数据会按该类型转换成对象)
     * @param sheetNo       sheet页号,从0开始
     * @param headRowNum    表头占的行数,从0开始(如果要连表头一起读出来则传0)
     */
    public static void asyncReadModel(File file, AnalysisEventListener<T> excelListener, Class clazz, Integer sheetNo, Integer headRowNum) {
        EasyExcelFactory.read(file, clazz, excelListener).sheet(sheetNo).headRowNumber(headRowNum).doRead();
    }

    /**
     * 异步按模型读取
     *
     * @param filePath
     * @param excelListener 监听器,在监听器中可以处理行数据LinkedHashMap,表头数据,异常处理等
     * @param clazz         模型的类类型(excel数据会按该类型转换成对象)
     * @param sheetNo       sheet页号,从0开始
     * @param headRowNum    表头占的行数,从0开始(如果要连表头一起读出来则传0)
     */
    public static void asyncReadModel(String filePath, AnalysisEventListener<T> excelListener, Class clazz, Integer sheetNo, Integer headRowNum) {
        EasyExcelFactory.read(filePath, clazz, excelListener).sheet(sheetNo).headRowNumber(headRowNum).doRead();
    }

    /**
     * 无模板写文件
     *
     * @param filePath
     * @param head     表头数据
     * @param data     表内容数据
     */
    public static void write(String filePath, List<List<String>> head, List<List<Object>> data) {
        EasyExcel.write(filePath).head(head).sheet().doWrite(data);
    }

    /**
     * 无模板写文件
     *
     * @param filePath
     * @param head      表头数据
     * @param data      表内容数据
     * @param sheetNo   sheet页号,从0开始
     * @param sheetName sheet名称
     */
    public static void write(String filePath, List<List<String>> head, List<List<Object>> data, Integer sheetNo, String sheetName) {
        EasyExcel.write(filePath).head(head).sheet(sheetNo, sheetName).doWrite(data);
    }

    /**
     * 根据excel模板文件写入文件
     *
     * @param filePath
     * @param templateFileName
     * @param headClazz
     * @param data
     */
    public static void writeTemplate(String filePath, String templateFileName, Class headClazz, List data) {
        EasyExcel.write(filePath, headClazz).withTemplate(templateFileName).sheet().doWrite(data);
    }

    /**
     * 根据excel模板文件写入文件
     *
     * @param filePath
     * @param templateFileName
     * @param data
     */
    public static void writeTemplate(String filePath, String templateFileName, List data) {
        EasyExcel.write(filePath).withTemplate(templateFileName).sheet().doWrite(data);
    }

    /**
     * 按模板写文件
     *
     * @param filePath
     * @param headClazz 表头模板
     * @param data      数据
     */
    public static void write(String filePath, Class headClazz, List data) {
        EasyExcel.write(filePath, headClazz).sheet().doWrite(data);
    }

    /**
     * 按模板写文件
     *
     * @param filePath
     * @param headClazz 表头模板
     * @param data      数据
     * @param sheetNo   sheet页号,从0开始
     * @param sheetName sheet名称
     */
    public static void write(String filePath, Class headClazz, List data, Integer sheetNo, String sheetName) {
        EasyExcel.write(filePath, headClazz).sheet(sheetNo, sheetName).doWrite(data);
    }

    /**
     * 按模板写文件
     *
     * @param filePath
     * @param headClazz    表头模板
     * @param data         数据
     * @param writeHandler 自定义的处理器,比如设置table样式,设置超链接、单元格下拉框等等功能都可以通过这个实现(需要注册多个则自己通过链式去调用)
     * @param sheetNo      sheet页号,从0开始
     * @param sheetName    sheet名称
     */
    public static void write(String filePath, Class headClazz, List data, WriteHandler writeHandler, Integer sheetNo, String sheetName) {
        EasyExcel.write(filePath, headClazz).registerWriteHandler(writeHandler).sheet(sheetNo, sheetName).doWrite(data);
    }

    /**
     * 按模板写文件(包含某些字段)
     *
     * @param filePath
     * @param headClazz   表头模板
     * @param data        数据
     * @param includeCols 包含字段集合,根据字段名称显示
     * @param sheetNo     sheet页号,从0开始
     * @param sheetName   sheet名称
     */
    public static void writeInclude(String filePath, Class headClazz, List data, Set<String> includeCols, Integer sheetNo, String sheetName) {
        EasyExcel.write(filePath, headClazz).includeColumnFiledNames(includeCols).sheet(sheetNo, sheetName).doWrite(data);
    }

    /**
     * 按模板写文件(排除某些字段)
     *
     * @param filePath
     * @param headClazz   表头模板
     * @param data        数据
     * @param excludeCols 过滤排除的字段,根据字段名称过滤
     * @param sheetNo     sheet页号,从0开始
     * @param sheetName   sheet名称
     */
    public static void writeExclude(String filePath, Class headClazz, List data, Set<String> excludeCols, Integer sheetNo, String sheetName) {
        EasyExcel.write(filePath, headClazz).excludeColumnFiledNames(excludeCols).sheet(sheetNo, sheetName).doWrite(data);
    }

    /**
     * 多个sheet页的数据链式写入
     * ExcelUtil.writeWithSheets(outputStream)
     * .writeModel(ExcelModel.class, excelModelList, "sheetName1")
     * .write(headData, data,"sheetName2")
     * .finish();
     *
     * @param outputStream
     */
    public static EasyExcelWriterFactory writeWithSheets(OutputStream outputStream) {
        EasyExcelWriterFactory excelWriter = new EasyExcelWriterFactory(outputStream);
        return excelWriter;
    }

    /**
     * 多个sheet页的数据链式写入
     * ExcelUtil.writeWithSheets(file)
     * .writeModel(ExcelModel.class, excelModelList, "sheetName1")
     * .write(headData, data,"sheetName2")
     * .finish();
     *
     * @param file
     */
    public static EasyExcelWriterFactory writeWithSheets(File file) {
        EasyExcelWriterFactory excelWriter = new EasyExcelWriterFactory(file);
        return excelWriter;
    }

    /**
     * 多个sheet页的数据链式写入
     * ExcelUtil.writeWithSheets(filePath)
     * .writeModel(ExcelModel.class, excelModelList, "sheetName1")
     * .write(headData, data,"sheetName2")
     * .finish();
     *
     * @param filePath
     */
    public static EasyExcelWriterFactory writeWithSheets(String filePath) {
        EasyExcelWriterFactory excelWriter = new EasyExcelWriterFactory(filePath);
        return excelWriter;
    }

    /**
     * 多个sheet页的数据链式写入(失败了会返回一个有部分数据的Excel)
     * ExcelUtil.writeWithSheets(response, exportFileName)
     * .writeModel(ExcelModel.class, excelModelList, "sheetName1")
     * .write(headData, data,"sheetName2")
     * .finish();
     *
     * @param response
     * @param exportFileName 导出的文件名称
     */
    public static EasyExcelWriterFactory writeWithSheetsWeb(HttpServletResponse response, String exportFileName) throws IOException {
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        // 这里URLEncoder.encode可以防止中文乱码
        String fileName = URLEncoder.encode(exportFileName, "UTF-8");
        response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
        EasyExcelWriterFactory excelWriter = new EasyExcelWriterFactory(response.getOutputStream());
        return excelWriter;
    }
}

2.EasyExcelWriterFactory

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;

import java.io.File;
import java.io.OutputStream;
import java.util.List;


public class EasyExcelWriterFactory {

    private int sheetNo = 0;
    private ExcelWriter excelWriter = null;

    public EasyExcelWriterFactory(OutputStream outputStream) {
        excelWriter = EasyExcel.write(outputStream).build();
    }

    public EasyExcelWriterFactory(File file) {
        excelWriter = EasyExcel.write(file).build();
    }

    public EasyExcelWriterFactory(String filePath) {
        excelWriter = EasyExcel.write(filePath).build();
    }

    /**
     * 链式模板表头写入
     *
     * @param headClazz 表头格式
     * @param data      数据 List<ExcelModel> 或者List<List<Object>>
     * @return
     */
    public EasyExcelWriterFactory writeModel(Class headClazz, List data, String sheetName) {
        excelWriter.write(data, EasyExcel.writerSheet(this.sheetNo++, sheetName).head(headClazz).build());
        return this;
    }

    /**
     * 链式自定义表头写入
     *
     * @param head
     * @param data      数据 List<ExcelModel> 或者List<List<Object>>
     * @param sheetName
     * @return
     */
    public EasyExcelWriterFactory write(List<List<String>> head, List data, String sheetName) {
        excelWriter.write(data, EasyExcel.writerSheet(this.sheetNo++, sheetName).head(head).build());
        return this;
    }

    /**
     * 使用此类结束后,一定要关闭流
     */
    public void finish() {
        excelWriter.finish();
    }
}

3.ExcelUtil

import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;

/**
 * @className com.yq.common.utils.ExcelUtil
 * @author: yangjie
 * @description: ExcelUtil
 */
@Slf4j
public class ExcelUtil {
    /**
     * 下载Excel
     *
     * @param response 请求response
     * @param fileName 下载文件名称 xxx.xlsx
     * @param filePath 下载文件路径 D://xxx/xxx
     */
    public static void downExcel(HttpServletResponse response, String fileName, String filePath) {
        // path是指想要下载的文件的路径
        File file = new File(filePath);
        ExcelUtil.downExcel(response, fileName, file);
    }


    /**
     * 下载Excel
     *
     * @param response 请求response
     * @param fileName 下载文件名称 xxx.xlsx
     * @param file     下载文件流
     */
    public static void downExcel(HttpServletResponse response, String fileName, File file) {
        FileInputStream fileInputStream = null;
        InputStream fis = null;
        OutputStream outputStream = null;
        try {
            // 将文件写入输入流
            fileInputStream = new FileInputStream(file);
            fis = new BufferedInputStream(fileInputStream);
            byte[] buffer = new byte[fis.available()];
            fis.read(buffer);
            fis.close();
            // 清空response
            response.reset();
            // 设置response的Header
            // 解决跨域问题,这句话是关键,对任意的域都可以,如果需要安全,可以设置成安前的域名
            response.addHeader("Access-Control-Allow-Origin", "*");
            response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
            response.setHeader("FileName", URLEncoder.encode(fileName, "UTF-8"));
            response.setHeader("Access-Control-Expose-Headers", "FileName");
            response.setCharacterEncoding("UTF-8");
            //Content-Disposition的作用:告知浏览器以何种方式显示响应返回的文件,用浏览器打开还是以附件的形式下载到本地保存
            //attachment表示以附件方式下载   inline表示在线打开   "Content-Disposition: inline; filename=文件名.mp3"
            // filename表示文件的默认名称,因为网络传输只支持URL编码的相关支付,因此需要将文件名URL编码后进行传输,前端收到后需要反编码才能获取到真正的名称
            response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
            // 告知浏览器文件的大小
            response.addHeader("Content-Length", "" + file.length());
            outputStream = new BufferedOutputStream(response.getOutputStream());
            response.setContentType("application/octet-stream");
            outputStream.write(buffer);
            outputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
            log.error("文件下载异常,{}", e);
        } finally {
            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

4.使用方式

实体类:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class AiAssistantForecastVo implements Serializable {
    private static final long serialVersionUID = -2986024804753822180L;
    @ExcelProperty(value = "细分品类", index = 0)
    private String presentName;
    @ExcelProperty(value = "大区", index = 1)
    private String regionNo;
    @ExcelProperty(value = "管理城市", index = 2)
    private String managingCityNo;
    @ExcelProperty(value = "店铺", index = 3)
    private String organKey;
    @ExcelProperty(value = "指标", index = 4)
    private String indicator;
    @ExcelProperty(value = "202336", index = 5)
    private String naturalYearWeek202336;
}

调用:

String fileName = "xxxx_" + System.currentTimeMillis() + ".xlsx";
String tempPath = "/home/uploads/";
String filePath = tempPath + fileName;
EasyExcelWriterFactory res = EasyExcelUtil.writeWithSheets(filePath)
.writeModel(AiAssistantForecastVo.class, aiAssistantForecastVoList, "报表数据");
res.finish();
ExcelUtil.downExcel(response, fileName, filePath);

5.如何处理不确定的字段?

  1. 通过反射拿到字段。
  2. 拿到字段后可以拿字段的注解。
  3. 根据字段注解的属性值可以确定需要填充的是哪个动态的字段。
  4. 通过使用 Reflect 反射工具类,很好的动态的填充了属性值。
//判断,周数相等的时候,才能塞进去
final Integer naturalYear = forecast.getNaturalYear();
final Integer naturalYearWeek = forecast.getNaturalYearWeek();
// 获取类的所有字段
Field[] fields = aiAssistantForecastVo.getClass().getDeclaredFields();
// 遍历字段
for (Field field : fields) {
    field.setAccessible(true);
    // 判断字段上是否有指定的注解
    if (field.isAnnotationPresent(ExcelProperty.class)) {
        // 获取字段上的注解
        ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
        // 获取注解的属性值
        final String[] value = annotation.value();
        final String property = value[0];
        if (StringUtils.equals(property, naturalYear.toString() + naturalYearWeek.toString())) {
            try {
                field.set(aiAssistantForecastVo, Objects.nonNull(Reflect.on(forecast).field(mapValue).get()) ? Reflect.on(forecast).field(mapValue).get().toString() : "");
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
}

6.需要注意的点

  • 如果是 docker 部署,记得设置挂载地址/home/uploads/
#docker run的时候设置挂载地址
-v /home/uploads:/home/uploads
  • 下载时间过长,可能需要设置 nginx 的超时时间
http {
  proxy_connect_timeout 300; #单位秒
    proxy_send_timeout 300; #单位秒
    proxy_read_timeout 300; #单位秒
    proxy_buffer_size 16k;
    proxy_buffers 4 64k;
    proxy_busy_buffers_size 128k;
    proxy_temp_file_write_size 128k;

}

觉得有用的话点个赞 👍🏻 呗。
❤️❤️❤️本人水平有限,如有纰漏,欢迎各位大佬评论批评指正!😄😄😄

💘💘💘如果觉得这篇文对你有帮助的话,也请给个点赞、收藏下吧,非常感谢!👍 👍 👍

🔥🔥🔥Stay Hungry Stay Foolish 道阻且长,行则将至,让我们一起加油吧!🌙🌙🌙

img

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/170508.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HALCON根据需要创建自定义函数

任务要求&#xff1a; 创建函数myfun(a,b,c)&#xff0c;输入浮点数a&#xff0c;b的值&#xff0c;计算c a b&#xff0c;将计算结果返回。 操作步骤&#xff1a; 1&#xff09;打开HDevelop程序 2&#xff09;打开函数菜单&#xff0c;选择“创建新函数”&#xff0c…

存储配置和挂载方式

存储配置 Iscsi简介 iSCSI 启动器&#xff0c;从本质上说&#xff0c;iSCSI 启动器是一个客户端设备&#xff0c;用于将请求连接并启动到服务器&#xff08;iSCSI 目标&#xff09;。 iSCSI 启动器有三种实现方式&#xff1a;可以完全基于硬件实现&#xff0c;比如 iSCSI H…

Conditional GAN

Text-to-Image 对于根据文字生成图像的问题&#xff0c;传统的做法就是训练一个NN&#xff0c;然后输入一段文字&#xff0c;输出对应一个图片&#xff0c;输出图片与目标图片越接近越好。存在的问题就是&#xff0c;比如火车对应的图片有很多张&#xff0c;如果用传统的NN来训…

在浏览器中使用WebRTC获取用户IP地址

本文翻译自 Discover WebRTC: Obtain User IP Addresses in the Browser&#xff0c;作者&#xff1a;Zack&#xff0c; 略有删改。 如果需要在程序中获取当前用户的IP&#xff0c;通常手段都是需要使用服务器。但现在借助WebRTC的强大功能&#xff0c;我们可以直接在浏览器客户…

基于springboot实现医院信管系统项目【项目源码+论文说明】

基于springboot实现医院信管系统演示 摘要 随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&#x…

重磅,瑞士药监局 发布 EU GMP附录1《无菌药品生产》官方解读!

近日&#xff0c;瑞士药监局发布了EU GMP附录1《无菌药品生产》&#xff08;同时也是PIC/S和WHO GMP附录1&#xff09;的解读文件&#xff0c;该文件侧重于新版EU、PIC/S和WHO GMP附录1的一些最重要的变化&#xff0c;也涵盖了长期以来反复引起问题的方面。反映了检查员对这些主…

python操作windows窗口,python库pygetwindow使用详解

文章目录 一、pygetwindow模块简介二、pygetwindow常用方法1、常用方法2、window常用方法 一、pygetwindow模块简介 pygetwindow是一个Python第三方库&#xff0c;用于获取、管理和操作窗口。它提供了一些方法和属性&#xff0c;使得在Python程序中可以轻松地执行各种窗口操作…

10个好用的Mac数据恢复软件推荐—恢复率高达99%

如果您正在寻找最好的 Mac 数据恢复软件来检索意外删除或丢失的文件&#xff0c;那么这里就是您的最佳选择。 我们理解&#xff0c;当您找不到 Mac 计算机或外部驱动器上保存的一些重要文件时&#xff0c;会感到多么沮丧和绝望。这些文件非常珍贵&#xff0c;无论出于何种原因…

通信原理板块——差错控制编码或纠错编码

微信公众号上线&#xff0c;搜索公众号小灰灰的FPGA,关注可获取相关源码&#xff0c;定期更新有关FPGA的项目以及开源项目源码&#xff0c;包括但不限于各类检测芯片驱动、低速接口驱动、高速接口驱动、数据信号处理、图像处理以及AXI总线等 1、背景 数字信号在传输过程中&…

(免费领源码)python#flask#mysql旅游数据可视化81319-计算机毕业设计项目选题推荐

摘要 信息化社会内需要与之针对性的信息获取途径&#xff0c;但是途径的扩展基本上为人们所努力的方向&#xff0c;由于站在的角度存在偏差&#xff0c;人们经常能够获得不同类型信息&#xff0c;这也是技术最为难以攻克的课题。针对旅游数据可视化等问题&#xff0c;对旅游数据…

AD9361寄存器功能笔记之本振频率设定

LO的产生过程如图&#xff1a; 各个模块都有高灵活性。 1、参考时钟即是AD9361全局参考时钟&#xff0c;可以是外接晶振的片上DCXO&#xff0c;或是外部输入的有驱动能力的时钟信号。根据FM-COMMS5的设计&#xff0c;参考时钟可以使用时钟Buffer 40MHz晶振构成的参考频率源。 …

实战 - 在Linux上部署各类软件

前言 为什么学习各类软件在Linux上的部署 在前面&#xff0c;我们学习了许多的Linux命令和高级技巧&#xff0c;这些知识点比较零散&#xff0c;同学们跟随着课程的内容进行练习虽然可以基础掌握这些命令和技巧的使用&#xff0c;但是并没有一些具体的实操能够串联起来这些知…

算法-简单-二叉树-翻转、对称

记录一下算法题的学习8 翻转二叉树 翻转二叉树题目 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 举例&#xff1a;给定root[5,3,7,2,4,6,10] 翻转成为root[5,7,3,10,6,4,2] 即所有的根节点的左右节点都要互换位置&#xff0c;输出的…

BUUCTF 菜刀666 1

BUUCTF:https://buuoj.cn/challenges 题目描述&#xff1a; 流量分析&#xff0c;你能找到flag吗 注意&#xff1a;得到的 flag 请包上 flag{} 提交 密文&#xff1a; 下载附件&#xff0c;解压得到一个.pcapng文件。 解题思路&#xff1a; 1、双击文件&#xff0c;打开wir…

两种典型的雷达框架,traditional chain (待深入了解)和Capon Beamforming Chain(已经了解)

如图1所示&#xff0c;第1种是被称作“traditional chain”, 它的处理思路是adc数据作range-FFT&#xff0c;再到doppler-FFT&#xff0c;构建range-Dopper map&#xff0c;再到cfar&#xff0c;最后对候选点作angle-FFT&#xff0c;当然&#xff0c;这是最经典的framework&…

查找二叉树中第k层中度为1的节点数量(可运行)

注意&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;如果没有输出结果&#xff0c;一定是输入的建树序列有错误&#xff0c;我建好了2棵树&#xff0c;如果没有输出结果&#xff0c;大家可以用这两棵树试。 main函数的btDepth(t,2)第二个参数是树的层数了k&…

SAP创建权限对象、角色、并分配角色

一、SU20&#xff1a;维护权限字段 二、SU21创建权限对象,分配权限字段: 三、SU24关联程序和自建权限对象&#xff08;标准tcode会默认存在标准权限对象&#xff09; 四、PFCG创建角色 五、SU01给用户分配角色 一、su20&#xff1a;维护权限字段 X点新建&#xff1a; 填入…

什么是策划能力?如何提高策划能力?

什么是策划能力&#xff1f; 通常我们理解的策划能力&#xff0c;大多指的是策划活动&#xff0c;比如举办一次活动先要进行活动策划&#xff0c;形成具体的行动方案&#xff0c;然后开展组织人力物力等资源&#xff0c;最终落地实施。策划能力包含活动策划&#xff0c;但又不…

vscode设置前进、后退快捷键

前言 在我们使用vscode编写程序时&#xff0c;经常需要在不同的文件之间跳来跳去&#xff0c;如果只是依靠个人记忆去操作会显得非常不方便。本文介绍如何设置vscode的前进、后退快捷键。 1 vscode设置前进、后退快捷键 点击“设置”图标&#xff0c;然后点击“键盘快捷方式…