Spring Boot下的一种导出Excel文件的代码框架
阅读原文时间:2023年07月12日阅读:1

​ 在Spring Boot项目中,将数据导出成Excel格式文件是常见的功能。与Excel文件导入类似,此处也用代码框架式的方式实现Excel文件导出,使得代码具有可重用性,并保持导出数据转换的灵活性。

​ 相对于导入Excel文件的处理,导出Excel文件要简单一些。这里的Excel文件支持xlsx格式。

​ 包括一个接口类ExcelExportable和一个Excel导出处理类ExcelExportHandler,以及支持ExcelExportable接口类的实体类。与基类相比,使用接口类的好处是可以实现多个接口类,而不会有多重继承的麻烦。这样实体类可以同时支持Excel和CSV格式文件的导出。

2.1、Excel导出处理类ExcelExportHandler

​ ExcelExportHandler类的代码如下:

package com.abc.questInvest.excel;

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

import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFFont;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import com.abc.questInvest.excel.ExcelExportable;

/**
 * @className    : ExcelExportHandler
 * @description    : Excel导出处理类
 *
 */
public class ExcelExportHandler<T extends ExcelExportable> {

    /**
     *
     * @methodName      : exportExcelFile
     * @description     : 导出Excel文件供下载
     * @param rowDataList   : 导出的数据列表
     * @param excelFilePath : Excel文件临时文件路径
     * @param sheetName: sheet页名称
     * @throws Exception    : 发生异常时,抛出
     *
     */
    public void exportExcelFile(List<T> rowDataList,String excelFilePath,String sheetName)
            throws Exception {
        if (rowDataList.size() == 0) {
            //必须要有导出数据,否则创建标题列失败
            throw new Exception("无导出数据.");
        }

        XSSFWorkbook wb = null;
        wb = new XSSFWorkbook();

        //创建sheet页
        XSSFSheet sheet = wb.createSheet(sheetName);

        //创建标题行
        createTitle(wb,sheet,rowDataList.get(0));

        //添加数据
        addSheetContent(sheet,wb,rowDataList);

        //输出文件数据供下载
        outputExcelFile(excelFilePath,wb);

    }   

    /**
     *
     * @methodName      : createTitle
     * @description     : 设置标题行
     * @param workbook  : workbook对象
     * @param sheet     : sheet对象
     * @throws Exception    : 发生异常时,抛出
     *
     */
    private void createTitle(XSSFWorkbook workbook, XSSFSheet sheet,T rowData) throws Exception{
        XSSFRow row = sheet.createRow(0);
        //取得标题行
        String[] arrTitles = rowData.outputTitleList();

        //设置列宽
        for (int i = 0; i < arrTitles.length; i++){
            //设置固定宽度,setColumnWidth的第二个参数的单位是1/256个字节宽度
            sheet.setColumnWidth(i,arrTitles[i].getBytes("UTF-8").length * 256);

            //设置自适应宽度,性能不高,故不用了
            //sheet.autoSizeColumn(i);
        }

        //设置为居中加粗
        XSSFCellStyle style = workbook.createCellStyle();
        XSSFFont font = workbook.createFont();
        font.setBold(true);
        style.setAlignment(HorizontalAlignment.CENTER);
        style.setFont(font);

        XSSFCell cell;
        for (int i = 0; i < arrTitles.length; i++){
            cell = row.createCell(i);
            cell.setCellValue(arrTitles[i]);
            cell.setCellStyle(style);
        }

    }

    /**
     *
     * @methodName      : addSheetContent
     * @description     : 为sheet对象添加数据行内容
     * @param sheet     : sheet对象
     * @param workbook  : workbook对象
     * @param rowDataList   : 数据行列表
     * @throws Exception    : 发生异常时,抛出
     *
     */
    private void addSheetContent(XSSFSheet sheet, XSSFWorkbook workbook, List<T> rowDataList)
             throws Exception
    {
        //单元格居中
        XSSFCellStyle style = workbook.createCellStyle();
        style.setAlignment(HorizontalAlignment.CENTER);

        //数据行下标从1开始
        int rowNum=1;
        //遍历导出数据行
        for(int i=0;i<rowDataList.size();i++){
            XSSFRow row = sheet.createRow(rowNum);
            XSSFCell cell;
            T rowData = rowDataList.get(i);
            String[] arrRow = rowData.outputList();
            for (int j = 0; j < arrRow.length; j++) {
                cell = row.createCell(j);
                cell.setCellValue(arrRow[j]);
                cell.setCellStyle(style);
            }
            rowNum++;
        }
    }   

    /**
     *
     * @methodName      : outputExcelFile
     * @description     : 输出Excel文件
     * @param filePath  : 输出的文件路径
     * @param workbook  : workbook对象
     * @throws Exception    : 发生异常时,抛出
     *
     */
    private void outputExcelFile(String filePath,XSSFWorkbook workbook) throws Exception{
        OutputStream outputStream = new FileOutputStream(new File(filePath));
        workbook.write(outputStream);
        outputStream.flush();
        outputStream.close();
    }
}

​ ExcelExportHandler支持泛型T,T限定必需支持ExcelExportable接口类。只要实体类实现ExcelExportable类的接口方法,就可以利用ExcelExportHandler的方法实现Excel文件导出。

2.2、可Excel导出的接口类ExcelExportable

​ ExcelExportable类的代码如下:

package com.abc.questInvest.excel;

/**
 * @className    : ExcelExportable
 * @description    : 可Excel导出的接口类,由POJO类实现
 *
 */
public interface ExcelExportable {

    /**
     *
     * @methodName      : outputTitleList
     * @description     : 输出标题列表,用于标题行
     * @return          : 字符串数组
     *
     */
    public String[] outputTitleList();

    /**
     *
     * @methodName      : outputList
     * @description     : 输出数据列表,用于数据行
     * @return          : 字符串数组
     *
     */
    public String[] outputList();
}

​ ExcelExportable类定义了2个接口方法:

  • outputTitleList方法,输出标题列表,用于标题行。
  • outputList方法,输出数据列表,用于数据行。

2.3、实体类例子

​ 对需要导出Excel文件的现有的实体类进行改造,使之支持ExcelExportable接口类。

​ 实体类为AnswerInfo,代码如下:

package com.abc.questInvest.entity;

import java.util.Date;

import javax.persistence.Column;

import com.abc.questInvest.excel.ExcelExportable;

import lombok.Data;

/**
 * @className    : AnswerInfo
 * @description    : 答卷信息类,支持Excel数据导出
 *
 */
@Data
public class AnswerInfo implements ExcelExportable {
    // 记录id
    @Column(name = "rec_id")
    private Integer recId;    

    // 发布任务id
    @Column(name = "task_id")
    private Integer taskId;

    // 问卷id
    @Column(name = "questionnaire_id")
    private Integer questionnaireId;

    // 问题编号
    @Column(name = "question_no")
    private Integer questionNo;

    // 答案
    @Column(name = "answer")
    private String answer;

    //========记录操作信息================
    // 操作人姓名
    @Column(name = "login_name")
    private String loginName;   

    // 记录删除标记,保留
    @Column(name = "delete_flag")
    private Byte deleteFlag;    

    // 创建时间
    @Column(name = "create_time")
    private Date createTime;

    // 更新时间
    @Column(name = "update_time")
    private Date updateTime;  

    //========实现ExcelExportable接口================

    //导出的Excel数据的列数
    private static final int COLUMN_COUNT = 5;

    /**
     *
     * @methodName      : outputTitleList
     * @description     : 输出标题列表,用于标题行
     * @return          : 字符串数组
     *
     */
    @Override
    public String[] outputTitleList() {
        String[] arrTitle = new String[COLUMN_COUNT];
        arrTitle[0] = "问卷ID";
        arrTitle[1] = "发布任务ID";
        arrTitle[2] = "记录ID";
        arrTitle[3] = "题号";
        arrTitle[4] = "答案";
        return arrTitle;
    }

    /**
     *
     * @methodName      : outputList
     * @description     : 输出数据列表,用于数据行
     * @return          : 字符串数组
     *
     */
    @Override
    public String[] outputList() {
        String[] arrRowData = new String[COLUMN_COUNT];

        //各属性字段,从数据库中取出,都为非null值
        //此处实现导出字段的物理含义转换和计算
        arrRowData[0] = questionnaireId.toString();
        arrRowData[1] = taskId.toString();
        arrRowData[2] = recId.toString();
        arrRowData[3] = questionNo.toString();
        arrRowData[4] = answer; 

        return arrRowData;
    }
}

2.4、单元测试

​ 下面进行Excel文件导出的单元测试。测试代码如下:

package com.abc.questInvest.excel;

import java.util.ArrayList;
import java.util.List;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import com.abc.questInvest.entity.AnswerInfo;

/**
 * @className    : ExcelExportHandlerTest
 * @description    : Excel文件导出测试
 *
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class ExcelExportHandlerTest {

    @Test
    public void exportExcelFileTest() {
        ExcelExportHandler<AnswerInfo> excelExp = new ExcelExportHandler<AnswerInfo>();

        AnswerInfo item = new AnswerInfo();
        item.setQuestionnaireId(1);
        item.setTaskId(1);
        item.setRecId(1);
        item.setQuestionNo(1);
        item.setAnswer("A");
        dataList.add(item);

        item = new AnswerInfo();
        item.setQuestionnaireId(1);
        item.setTaskId(1);
        item.setRecId(1);
        item.setQuestionNo(2);
        item.setAnswer("B");
        dataList.add(item);

        item = new AnswerInfo();
        item.setQuestionnaireId(1);
        item.setTaskId(1);
        item.setRecId(1);
        item.setQuestionNo(3);
        item.setAnswer("A");
        dataList.add(item);

        item = new AnswerInfo();
        item.setQuestionnaireId(1);
        item.setTaskId(1);
        item.setRecId(2);
        item.setQuestionNo(1);
        item.setAnswer("B");
        dataList.add(item);

        item = new AnswerInfo();
        item.setQuestionnaireId(1);
        item.setTaskId(1);
        item.setRecId(2);
        item.setQuestionNo(2);
        item.setAnswer("B");
        dataList.add(item);

        item = new AnswerInfo();
        item.setQuestionnaireId(1);
        item.setTaskId(1);
        item.setRecId(2);
        item.setQuestionNo(3);
        item.setAnswer("C");
        dataList.add(item);

        String property = System.getProperty("user.dir");
        String filePath = property + "\\answer_data_Q1_T1.xlsx";
        String sheetName = "Q1_T1";

        List<AnswerInfo> dataList = new ArrayList<AnswerInfo>();

        try {
            excelExp.exportExcelFile(dataList,filePath, sheetName);
        }catch(Exception e) {
            e.printStackTrace();
        }

    }
}

执行测试代码,可以看到导出的Excel文件,文件内容如下:

​ 在导出生成了Excel文件后,只需与文件下载代码结合起来,就可以实现Excel文件下载了。

​ 文件下载,可作为静态的公共方法,放入工具类中。代码如下:

    /**
     *
     * @methodName      : download
     * @description     : 下载指定路径的文件
     * @param response  : reponse对象
     * @param filePath  : 需要下载的文件路径
     * @param contentType   : response header中的ContentType,常用取值如下:
     *      普通二进制文件 : application/octet-stream
     *      Excel文件     : application/vnd.ms-excel
     *      文本文件        : text/plain; charset=utf-8
     *      html文件      : text/html; charset=utf-8
     *      xml文件           : text/xml; charset=utf-8
     *      jpeg文件      : image/jpeg
     * @throws Exception    : 异常发生时,抛出。没有异常,说明下载成功。
     *
     */
    public static void download(HttpServletResponse response,String filePath,
             String contentType) throws Exception{

        File file = new File(filePath);
        if (file.exists()) {
            //设置读取流的缓存为1K
            byte[] buffer = new byte[1024];
            FileInputStream fis = null;
            BufferedInputStream bis = null;
            //设置ContentType
            response.setContentType(contentType);
            // 下载文件能正常显示中文
            String filename = filePath.substring(filePath.lastIndexOf("/")+1);
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
            //获取输入流
            fis = new FileInputStream(file);
            bis = new BufferedInputStream(fis);
            //输出流
            OutputStream os = response.getOutputStream();
            int len = bis.read(buffer);
            while (len != -1) {
                os.write(buffer, 0, len);
                len = bis.read(buffer);
            }             

            //关闭流
            if (bis != null) {
                bis.close();
            }
            if (fis != null) {
                fis.close();
            }
        }
    }

​ 调用download方法,contentType参数取值为“application/vnd.ms-excel”即可。