package com.first.springboot.util;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.first.springboot.vo.AllExcelDataDTO;
import com.first.springboot.vo.ExcelDTO;
import com.first.springboot.vo.ExcelDataDTO;
import com.first.springboot.vo.ExcelMsgDTO;
import com.sun.deploy.net.URLEncoder;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* 项目名称:spring-boot-first
* 类名称:EasyExcelUtil
* 类描述:TDD
* 创建人:yingx
* 创建时间: 2022/3/13
* 修改人:yingx
* 修改时间: 2022/3/13
* 修改备注:
*/
public class EasyExcelUtil {
public static void exportExcel(String fileName, HttpServletResponse response) throws IOException {
// 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
ExcelWriterBuilder write = EasyExcel.write(response.getOutputStream(), AllExcelDataDTO.class);
WriteSheet sheet = write.sheet("模板").build();
ExcelWriter writer = write.build();
// 对同一个sheet中的分段数据进行读写到一个sheet中
writer.write(new ArrayList<>(), sheet); // 数据1
writer.write(new ArrayList<>(), sheet); // 数据2
writer.finish();
}
public static void exportExcel(List<String> redisKey, WriteSheet sheet, ExcelWriterBuilder write, HttpServletResponse response) {
// redisKey.fro
ExcelWriterBuilder write = EasyExcel.write(response.getOutputStream(), AllExcelDataDTO.class);
try {
// 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面
for (int i = 0; i < 5; i++) {
// 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样
WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).build();
// 分页去数据库查询数据 这里可以去数据库查询每一页的数据
excelWriter.write(data, writeSheet);
}
} finally {
// 千万别忘记finish 会帮忙关闭流
if (excelWriter != null) {
excelWriter.finish();
}
}
}
public static List<AllExcelDataDTO> headData(List<ExcelDTO> excelData) {
List<AllExcelDataDTO> result = new ArrayList<>();
AllExcelDataDTO allExcelData = null;
for (ExcelDTO data : excelData) {
for (ExcelDataDTO excelDa : data.getExcelData()) {
allExcelData = new AllExcelDataDTO();
allExcelData.setDataName(excelDa.getName());
allExcelData.setDataAge(excelDa.getAge());
}
for (ExcelMsgDTO excelMsg : data.getExcelMsg()) {
allExcelData = new AllExcelDataDTO();
allExcelData.setMsgName(excelMsg.getName());
allExcelData.setMsgAge(excelMsg.getAge());
}
result.add(allExcelData);
}
return result;
}
}
package com.first.springboot.util;
import java.lang.annotation.*;
/**
* 自定义注解,用于判断是否需要合并以及合并的主键
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface PlanReportMerge {
/**
* 是否需要合并单元格
*/
boolean needMerge() default false;
/**
* 是否是主键,即该字段相同的行合并
*/
boolean isPk() default false;
}
package com.first.springboot.util;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.write.handler.RowWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
/**
* 自定义单元格合并策略
*/
public class PlanReportMergeStrategy implements RowWriteHandler {
/**
* 主键下标
*/
private Integer pkIndex;
/**
* 需要合并的列的下标集合
*/
private List<Integer> needMergeColumnIndex = new ArrayList<>();
/**
* DTO数据类型
*/
private Class<?> elementType;
public PlanReportMergeStrategy(Class<?> elementType) {
this.elementType = elementType;
}
@Override
public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer relativeRowIndex, Boolean isHead) {
// 如果是标题,则直接返回
if (isHead) {
return;
}
// 获取当前sheet
Sheet sheet = writeSheetHolder.getSheet();
// 获取标题行
Row titleRow = sheet.getRow(0);
if (null == pkIndex) {
this.lazyInit(writeSheetHolder);
}
// 判断是否需要和上一行进行合并
// 不能和标题合并,只能数据行之间合并
if (row.getRowNum() <= 1) {
return;
}
// 获取上一行数据
Row lastRow = sheet.getRow(row.getRowNum() - 1);
// 将本行和上一行是同一类型的数据(通过主键字段进行判断),则需要合并
if (lastRow.getCell(pkIndex).getStringCellValue().equalsIgnoreCase(row.getCell(pkIndex).getStringCellValue())) {
for (Integer needMerIndex : needMergeColumnIndex) {
CellRangeAddress cellRangeAddress = new CellRangeAddress(row.getRowNum() - 1, row.getRowNum(),
needMerIndex, needMerIndex);
sheet.addMergedRegionUnsafe(cellRangeAddress);
}
}
}
/**
* 初始化主键下标和需要合并字段的下标
*/
private void lazyInit(WriteSheetHolder writeSheetHolder) {
// 获取当前sheet
Sheet sheet = writeSheetHolder.getSheet();
// 获取标题行
Row titleRow = sheet.getRow(0);
// 获取DTO的类型
Class<?> eleType = this.elementType;
// 获取DTO所有的属性
Field[] fields = eleType.getDeclaredFields();
// 遍历所有的字段,因为是基于DTO的字段来构建excel,所以字段数 >= excel的列数
for (Field theField : fields) {
// 获取@ExcelProperty注解,用于获取该字段对应在excel中的列的下标
ExcelProperty easyExcelAnno = theField.getAnnotation(ExcelProperty.class);
// 为空,则表示该字段不需要导入到excel,直接处理下一个字段
if (null == easyExcelAnno) {
continue;
}
// 获取自定义的注解,用于合并单元格
PlanReportMerge customMerge = theField.getAnnotation(PlanReportMerge.class);
// 没有@CustomMerge注解的默认不合并
if (null == customMerge) {
continue;
}
for (int index = 0; index < fields.length; index++) {
Cell theCell = titleRow.getCell(index);
// 当配置为不需要导出时,返回的为null,这里作一下判断,防止NPE
if (null == theCell) {
continue;
}
// 将字段和excel的表头匹配上
if (easyExcelAnno.value()[0].equalsIgnoreCase(theCell.getStringCellValue())) {
if (customMerge.isPk()) {
pkIndex = index;
}
if (customMerge.needMerge()) {
needMergeColumnIndex.add(index);
}
}
}
}
// 没有指定主键,则异常
if (null == this.pkIndex) {
throw new IllegalStateException("使用@PlanReportMerge注解必须指定主键");
}
}
}