爱不释手的Excel导出工具类

引言

最近了不起做的需求中有一个需求是要求导出Excel表格,有大约十几张表需要导出吧。

那么问题来了,要是你来实现,你会怎么做?

简易实现导出Excel

一般快速导出可以这么实现:

代码语言:javascript
复制
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

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

public class ExcelExportExample {

public static void main(String[] args) {
    Workbook workbook = new XSSFWorkbook();
    Sheet sheet = workbook.createSheet("Sheet1");

    // 创建表头行
    Row headerRow = sheet.createRow(0);
    Cell headerCell1 = headerRow.createCell(0);
    headerCell1.setCellValue("姓名");
    Cell headerCell2 = headerRow.createCell(1);
    headerCell2.setCellValue("年龄");

    // 添加数据行
    Row dataRow1 = sheet.createRow(1);
    Cell dataCell1 = dataRow1.createCell(0);
    dataCell1.setCellValue("张三");
    Cell dataCell2 = dataRow1.createCell(1);
    dataCell2.setCellValue(25);

    Row dataRow2 = sheet.createRow(2);
    Cell dataCell3 = dataRow2.createCell(0);
    dataCell3.setCellValue("李四");
    Cell dataCell4 = dataRow2.createCell(1);
    dataCell4.setCellValue(30);

    // 导出Excel文件
    try (FileOutputStream outputStream = new FileOutputStream("example.xlsx")) {
        workbook.write(outputStream);
        System.out.println("Excel导出成功!");
    } catch (IOException e) {
        System.out.println("导出Excel文件时出现错误:" + e.getMessage());
    } finally {
        try {
            workbook.close();
        } catch (IOException e) {
            System.out.println("关闭Workbook时出现错误:" + e.getMessage());
        }
    }
}

}

那么思考一下,了不起要做好多的重复工作啊,我还要来设置表头,装填每个行每个字段的数据。

构思导出Excel工具类

1.使用工厂设计模式

在导出不同的表格时,我们一定会创建表格,那么我们可以将创建这步工厂化。

首先,定义一个抽象的工厂类 WorkbookFactory,用于创建工作簿对象:

代码语言:javascript
复制
publicabstractclassWorkbookFactory{
publicabstract Workbook createWorkbook();

publicabstract Sheet createSheet(Workbook workbook, String sheetName);

publicabstractvoidcreateHeaderRow(Sheet sheet);

publicabstractvoidaddDataRows(Sheet sheet);
}

然后,创建具体的工厂类 ApachePOIWorkbookFactory,继承自 WorkbookFactory,实现具体的工作簿创建和表格操作方法:

代码语言:javascript
复制
publicclassApachePOIWorkbookFactoryextendsWorkbookFactory{
@Override
public Workbook createWorkbook(){
returnnew XSSFWorkbook();
}

@Override
public Sheet createSheet(Workbook workbook, String sheetName){
return workbook.createSheet(sheetName);
}

@Override
publicvoidcreateHeaderRow(Sheet sheet){
// 默认实现为空,具体的子类可以重写该方法来设置表头样式和内容
}

@Override
publicvoidaddDataRows(Sheet sheet){
// 默认实现为空,具体的子类可以重写该方法来添加数据行和设置样式
}
}

接下来,创建一个具体的工厂子类 CustomizedWorkbookFactory,继承自 ApachePOIWorkbookFactory,用于自定义不同表格的表头、样式和数据:

代码语言:javascript
复制
publicclassCustomizedWorkbookFactoryextendsApachePOIWorkbookFactory{
@Override
publicvoidcreateHeaderRow(Sheet sheet){
Row headerRow = sheet.createRow(0);
CellStyle headerStyle = sheet.getWorkbook().createCellStyle();
Font headerFont = sheet.getWorkbook().createFont();
headerFont.setBold(true);
headerStyle.setFont(headerFont);

// 自定义设置表头样式和内容
Cell cell1 = headerRow.createCell(0);
cell1.setCellValue("姓名");
cell1.setCellStyle(headerStyle);

    Cell cell2 = headerRow.createCell(1);
    cell2.setCellValue("年龄");
    cell2.setCellStyle(headerStyle);
}

@Override
publicvoidaddDataRows(Sheet sheet){
// 自定义添加数据行和样式
Row dataRow1 = sheet.createRow(1);
CellStyle dataCellStyle = sheet.getWorkbook().createCellStyle();
dataCellStyle.setBorderBottom(BorderStyle.THIN);
dataCellStyle.setBorderTop(BorderStyle.THIN);
dataCellStyle.setBorderLeft(BorderStyle.THIN);
dataCellStyle.setBorderRight(BorderStyle.THIN);

    Cell cell1 = dataRow1.createCell(0);
    cell1.setCellValue("张三");
    cell1.setCellStyle(dataCellStyle);

    Cell cell2 = dataRow1.createCell(1);
    cell2.setCellValue(25);
    cell2.setCellStyle(dataCellStyle);
}

}

最后,在导出数据时,使用工厂类的方法创建工作簿并自定义表头和数据:

代码语言:javascript
复制
publicclassExcelExportExample{
publicstaticvoidmain(String[] args){
WorkbookFactory workbookFactory = new CustomizedWorkbookFactory();

    Workbook workbook = workbookFactory.createWorkbook();
    Sheet sheet = workbookFactory.createSheet(workbook, "Sheet1");

    workbookFactory.createHeaderRow(sheet);
    workbookFactory.addDataRows(sheet);

// 导出工作簿...
}
}

在上述示例中,通过继承和重写工厂类的方法,我们可以自定义不同表格的表头样式、内容、数据行以及其他样式设置。

这种方式我们可以根据需要灵活地定制不同的表格导出功能。

2. 使用注解

在前面的导出方法中,了不起觉得要把表头传递进去也很费事。

可以加个注解在我导出的实体对象字段上,我做好表头设置,标记好字段就可以了。

代码语言:javascript
复制
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelField {
/**
* 表头名称
*/
String name();

/**
 * 排序
 */
int sort() default 0;

}

3. 使用反射

每个字段我也不想去自己填了,用反射的方式找到那个具体的字段。

代码语言:javascript
复制
/**
* 创建表格主体
* @param sheet Sheet
* @param data 数据
*/
private static void createBody(Sheet sheet, List<?> data) {
for (int i = 0; i < data.size(); i++) {
Row row = sheet.createRow(i + 1);
List<ExcelHeader> headers = getHeaders(data.get(i).getClass());
for (int j = 0; j < headers.size(); j++) {
Cell cell = row.createCell(j);
try {
Field field = data.get(i).getClass().getDeclaredField(headers.get(j).getField());
field.setAccessible(true);
Object value = field.get(data.get(i));
if (value != null) {
cell.setCellValue(value.toString());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

代码实现

在我这次的具体开发中,Excel格式都是一样的,故我这里就不用工厂方法获取Book了。

1. 导入依赖

Spring Boot项目中添加Apache POI和Web Starter依赖。

pom.xml文件中添加以下依赖:

代码语言:javascript
复制
<!-- Apache POI -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>

<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

2. 工具类封装

代码语言:javascript
复制
public class ExcelUtils {

/**
 * 导出Excel
 * @param response HttpServletResponse
 * @param fileName 文件名
 * @param data 数据
 * @throws IOException
 */
public static void exportExcel(HttpServletResponse response, String fileName, List&lt;?&gt; data) throws IOException {
    response.setContentType(&#34;application/octet-stream&#34;);
    response.setHeader(&#34;Content-Disposition&#34;, &#34;attachment;filename=&#34; + fileName + &#34;.xls&#34;);
    OutputStream outputStream = response.getOutputStream();
    exportExcel(data, outputStream);
    outputStream.flush();
    outputStream.close();
}

/**
 * 导出Excel
 * @param data 数据
 * @param outputStream OutputStream
 * @throws IOException
 */
public static void exportExcel(List&lt;?&gt; data, OutputStream outputStream) throws IOException {
    Workbook workbook = new HSSFWorkbook();
    Sheet sheet = workbook.createSheet();
    createHeader(sheet, data.get(0).getClass());
    createBody(sheet, data);
    workbook.write(outputStream);
}

/**
 * 创建表头
 * @param sheet Sheet
 * @param clazz 类
 */
private static void createHeader(Sheet sheet, Class&lt;?&gt; clazz) {
    Row row = sheet.createRow(0);
    List&lt;ExcelHeader&gt; headers = getHeaders(clazz);
    for (int i = 0; i &lt; headers.size(); i++) {
        Cell cell = row.createCell(i);
        cell.setCellValue(headers.get(i).getName());
        CellStyle cellStyle = sheet.getWorkbook().createCellStyle();
        Font font = sheet.getWorkbook().createFont();
        font.setBold(true);
        font.setColor(IndexedColors.WHITE.getIndex());
        cellStyle.setFillForegroundColor(IndexedColors.BLUE.getIndex());
        cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        cellStyle.setFont(font);
        cell.setCellStyle(cellStyle);
    }
}

/**
 * 创建表格主体
 * @param sheet Sheet
 * @param data 数据
 */
private static void createBody(Sheet sheet, List&lt;?&gt; data) {
    for (int i = 0; i &lt; data.size(); i++) {
        Row row = sheet.createRow(i + 1);
        List&lt;ExcelHeader&gt; headers = getHeaders(data.get(i).getClass());
        for (int j = 0; j &lt; headers.size(); j++) {
            Cell cell = row.createCell(j);
            try {
                Field field = data.get(i).getClass().getDeclaredField(headers.get(j).getField());
                field.setAccessible(true);
                Object value = field.get(data.get(i));
                if (value != null) {
                    cell.setCellValue(value.toString());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

/**
 * 获取表头信息
 * @param clazz 类
 * @return 表头信息
 */
private static List&lt;ExcelHeader&gt; getHeaders(Class&lt;?&gt; clazz) {
    List&lt;Field&gt; fields = new ArrayList&lt;&gt;();
    Class&lt;?&gt; tempClass = clazz;
    while (tempClass != null) {
        Field[] declaredFields = tempClass.getDeclaredFields();
        for (Field field : declaredFields) {
            fields.add(field);
        }
        tempClass = tempClass.getSuperclass();
    }
    Map&lt;String, Field&gt; fieldMap = fields.stream().collect(Collectors.toMap(Field::getName, field -&gt; field));
    List&lt;ExcelHeader&gt; headers = new ArrayList&lt;&gt;();
    for (Field field : fields) {
        Annotation[] annotations = field.getDeclaredAnnotations();
        for (Annotation annotation : annotations) {
            if (annotation instanceof ExcelField) {
                ExcelField excelField = (ExcelField) annotation;
                ExcelHeader header = new ExcelHeader();
                header.setName(excelField.name());
                header.setField(field.getName());
                headers.add(header);
            }
        }
    }
    return headers.stream().sorted((h1, h2) -&gt;
            Integer.compare(fieldMap.get(h1.getField()).
                    getAnnotation(ExcelField.class).sort(), fieldMap.get(h2.getField()).
                    getAnnotation(ExcelField.class).sort())).collect(Collectors.toList());
}

/**
 * 表头信息
 */
private static class ExcelHeader {
    private String name;
    private String field;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getField() {
        return field;
    }

    public void setField(String field) {
        this.field = field;
    }
}

}

3. 注解使用

直接使用在你的

代码语言:javascript
复制
@ApiModel("活动记录")
@Data
public class ActiveLogDto {
@JSONField(name = "id")
private String id;
@ApiModelProperty("活动id")
private String activeId;

@ApiModelProperty("活动名称")
private String activeName;

@ApiModelProperty("类型")
private String type;

@ApiModelProperty("用户角色")
private Integer userAwardType;

@ApiModelProperty("用户角色")
@ExcelField(name = "奖励类型", sort = 1)
private String userAwardTypeDesc;

@ApiModelProperty("用户邮箱")
@ExcelField(name = "用户邮箱", sort = 2)
private String userEmail;

@ApiModelProperty("记录")
@ExcelField(name = "动作", sort = 3)
private String record;

@ApiModelProperty("订单记录")
@ExcelField(name = "订单奖励", sort = 8)
private String orderRecord;

@ApiModelProperty("下单时间")
@ExcelField(name = "下单时间", sort = 7)
private String orderTime;

@ApiModelProperty("订单编号")
@ExcelField(name = "订单ID", sort = 5)
private String orderNo;

@ApiModelProperty("订单金额")
@ExcelField(name = "下单金额", sort = 6)
private BigDecimal orderAmount;

@ApiModelProperty("订单币种:RMB...")
@ExcelField(name = "下单币种", sort = 7)
private String orderCurrency;

@ApiModelProperty("邮箱")
@ExcelField(name = "邮箱", sort = 11)
private String referrerEmail;

@ApiModelProperty("推荐码")
@ExcelField(name = "推荐码", sort = 4)
private String referrerCode;

@ApiModelProperty("金额")
@ExcelField(name = "金额", sort = 9)
private BigDecimal orderAwardAmount;

@ApiModelProperty("时间")
@ExcelField(name = "时间", sort = 10)
private String orderAwardSendTime;

@ApiModelProperty("createTime")
@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;

}

4. 代码具体调用

代码语言:javascript
复制
public Result exportLogExcel(String id, HttpServletResponse httpServletResponse) {
List<ActiveLogDto> dtoList = this.getListById(id);

    if (excelList.isEmpty()) {
        throw new BusinessException(&#34;xxx&#34;, messageSource.getMessage(&#34;xxx&#34;, null,
                LocaleContextHolder.getLocale()));
    }

    String fileName = excelList.get(0).getxxx_name();
    try {
        ExcelUtils.exportExcel(httpServletResponse,fileName + &#34;操作记录&#34;,excelList);
    } catch (IOException e) {
        log.error(&#34;导出异常:&#34;,e);
        throw new BusinessException(&#34;xxx&#34;, messageSource.getMessage(&#34;xxx&#34;, null,
                LocaleContextHolder.getLocale()));
    }
    return Result.success();
}

5. 配合前端页面导出

后端接口

代码语言:javascript
复制
@GetMapping("/list/export/{id}")
@ApiOperation(notes = "导出记录", value = "导出记录", httpMethod = "GET")
public Result listExportLogs(@PathVariable String id, HttpServletResponse httpServletResponse) {
return xxService.exportLogExcel(id,httpServletResponse);
}

前端代码

代码语言:javascript
复制
<template>
<div>
<button @click="exportLogs">导出记录</button>
</div>
</template>

<script>
export default {
methods: {
exportLogs() {
const id = "your_id"; // 替换为实际的ID
window.location.href = 域名/list/export/${id};
},
},
};
</script>

导出后:

总结

了不起就工作中的一个小需求进行实现,封装了一个简易的导出excel工具类。

过程中也探讨了一些设计模式和思想,不过真正代码实践的时候和想的还是有一定差距的。

不要因为技术而技术,更多的是为了业务服务。