JAVA:记一次 excel 导出需求
===================
公司业务大范围改造,包括一些代码重构,数据来源迁移等,其中很小的一个点就是对列表数据的导出。当时参考了一下公司前辈写的代码。代码不好贴出来,但是流程基本如下:
public void exportData(Object...params){
// 参数校验,包括非法校验和时间跨度校验
// 封装Workbook(在方法里进行了导出数据的查询)
// 设置名字
// 设置response参数
// 具体的数据写出
// 关闭相关资源
}
这么看过来似乎流程有些混乱,但是没法去评判之前人写代码的好与坏,毕竟不知道当时是什么样的开发周期和情况。
文件名设置,文件内容写出,参数设置,关闭资源。等等这些,完全可以封装到一起,不然每次写导出,都和这个一样写一串,对于后来者而言就是灾难了。
我想要的是抽成公用的方法,参数是传入具体的需要导出的list数据,然后方法里把别的都干了。当然,就单单这个功能而言,easyexcel有更加完备的技术实现。我看了看代码里,并没有引入easyxcel的依赖,本着小心谨慎引入新依赖的想法,决定自己封一个比较类似的。
接下来就直接贴代码了,可以将如下几个类放在一个包下。
ExcelWriteDataBuilder用于构建导出时需要的文件名,sheet名等数据:
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.io.OutputStream;
@Slf4j
@Data
public class ExcelWriteDataBuilder {
private String fileName;
private String sheetName;
private Class clazz;
private OutputStream outputStream;
public static ExcelWriteDataBuilder outputStream(OutputStream outputStream) {
ExcelWriteDataBuilder data = new ExcelWriteDataBuilder();
data.outputStream = outputStream;
return data;
}
public ExcelWriteDataBuilder clazz(Class clazz) {
this.clazz = clazz;
return this;
}
public ExcelWriteDataBuilder sheetName(String sheetName) {
this.sheetName = sheetName;
return this;
}
public ExcelExportHandler fileName(String fileName) {
this.fileName = fileName;
ExcelExportHandler excelExportHandler = new ExcelExportHandler();
excelExportHandler.setWriteDataBuilder(this);
return excelExportHandler;
}
}
ExcelExportHandler作为方法调用入口
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
@Data
@Slf4j
public class ExcelExportHandler {
private ExcelWriteDataBuilder writeDataBuilder;
public <T extends ExcelWriteTool,R> void write(List<R> list, Class<T> clazz) {
try {
log.info( "write excel start ==== ");
T t = clazz.newInstance();
t.setWriteDataBuilder(writeDataBuilder);
t.doAction(list);
} catch (Exception e) {
log.error("write error e :", e);
}
}
}
ExcelWriteTool 具体的写excel逻辑,同时支持字类对它进行扩展,支持自定义的excel格式,比如前后增加特定提示语等。
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Slf4j
@Data
public class ExcelWriteTool {
private ExcelWriteDataBuilder writeDataBuilder;
// 当前行数
private Integer index;
private XSSFWorkbook wb;
private XSSFSheet sheet;
// 需要写出的属性
private List<Field> fields;
@SneakyThrows
public void pre() {
// 前置操作
HttpServletResponse resp = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
if (resp != null) {
log.info("ExcelWriteTool.pre resp set ");
resp.reset();
resp.setContentType("application/vnd.ms-excel;charset=utf-8");
resp.setHeader("Content-Disposition", "attachment;filename=" + new String((writeDataBuilder.getFileName() + ".xlsx").getBytes(), "iso-8859-1"));
}
}
public <T> void action(List<T> data) {
// 写表头
writeHead();
// 写数据
writeData(data);
}
public <T> void writeData(List<T> data) {
// 具体的写数据逻辑
log.info("writeData start ");
for (T item : data) {
XSSFRow row = sheet.createRow(index++);
for (int i = INT_ZERO; i < fields.size(); i++) {
val field = fields.get(i);
createCell(row, i, ReflectUtils.getValue(item, field, String.class));
}
}
log.info("writeData end ");
}
public void writeHead() {
log.info("writeHead start ");
// 可能在实现类中有些前置操作属性发生变化,所以判空
index = Optional.ofNullable(index).orElse(INT_ZERO);
wb = Optional.ofNullable(wb).orElseGet(XSSFWorkbook::new);
sheet = Optional.ofNullable(sheet).orElseGet(() -> wb.createSheet(writeDataBuilder.getSheetName()));
XSSFRow titleRow = sheet.createRow(index);
// 获取实体的所有属性
List<Field> fs = ReflectUtils.getFields(writeDataBuilder.getClazz());
fields = Optional.ofNullable(fields).orElseGet(() -> fs.stream()
// 过滤 不可见的
.filter(item -> item.getAnnotation(ExcelExport.class).visible())
// 排序
.sorted(Comparator.comparingInt(item -> item.getAnnotation(ExcelExport.class).index()))
.collect(Collectors.toList()));
for (int i = INT_ZERO; i < fields.size(); i++) {
String titleCell = fields.get(i).getAnnotation(ExcelExport.class).desc();
String[] cellPair = titleCell.split(COLON);
createCell(titleRow, i, cellPair[INT_ZERO]);
String width = cellPair.length == INT_ONE ? DEFAULT_EXCEL_WIDTH : cellPair[INT_ONE];
sheet.setColumnWidth(i, 256 * Integer.parseInt(width) + 184);
}
// index自增
index++;
log.info("ExcelWriteTool.writeHead write head success");
}
public <T> void doAction(List<T> data) {
try {
pre();
action(data);
after();
} catch (Exception e) {
log.error("ExcelWriteTool.doAction error e:", e);
} finally {
// 关闭资源
closeResource();
}
}
private void closeResource() {
// 关闭资源
try {
if (writeDataBuilder.getOutputStream() != null) {
writeDataBuilder.getOutputStream().close();
}
} catch (Exception e) {
log.error("ExcelWriteTool.after close outStream error e:", e);
}
if (wb != null) {
try {
wb.close();
} catch (IOException e) {
log.error("ExcelWriteTool.after close wb error e:", e);
}
}
}
public void after() throws IOException {
log.info("ExcelWriteTool.after start");
wb.write(writeDataBuilder.getOutputStream());
}
private static Cell createCell(Row row, int cellNum, String value) {
Cell cell = row.createCell(cellNum, Cell.CELL_TYPE_STRING);
cell.setCellValue(getNotNullStr(value));
return cell;
}
}
ExcelExport定义注解,对实体需要写出的字段进行标识
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelExport {
/**
* 字段描述:单元格宽度
*/
String desc();
/**
* index
*/
int index();
/**
* 可见性
*/
boolean visible() default true;
}
注解使用场景,需要导出的实体类上
MerchantApplyWideWriter继承ExcelWriteTool,可以进行一些功能的扩展
import java.util.List;
public class MerchantApplyWideWriter extends ExcelWriteTool{
public <T> void action(List<T> data) {
// 进行定制化的操作...
super.action(data);
}
}
使用姿势
// write Data
ExcelWriteDataBuilder.outputStream(resp.getOutputStream())
.sheetName("商家入驻申请信息")
.clazz(MerchantApplyWideExcelData.class)
.fileName("商家入驻申请信息")
.write(exportData, MerchantApplyWideWriter.class);
其中clazz里面是需要导出的实体类型,write里面放待导出数据和导出的处理器。
这样的话,每次新的导出需求,需要修改的地方,只有新增实体,实体上写注解,然后如果没有特定的格式问题,甚至都可以直接照搬上面的使用姿势,大大缩短了开发流程,也更加规范和统一。
当然了,代码肯定会存在一些瑕疵,比如一些魔法值的处理,类的冗余,或者其他可能出现优化的地方。
时间问题,这些目前没有处理,也欢迎大佬指证和补充。
原文链接: https://juejin.cn/post/7354786868880719898
文章收集整理于网络,请勿商用,仅供个人学习使用,如有侵权,请联系作者删除,如若转载,请注明出处:http://www.cxyroad.com/17902.html