当前位置: 首页 > news >正文

EasyExcel导出极致封装 含枚举转换 分页导出

文章目录

  • 一、优势
  • 二、使用案例
    • 1.示例代码
    • 2.效果
  • 三、源码
    • 枚举注解
    • 动态列接口
    • 工具类


一、优势

  • 1.easyexcel的枚举值转换需要 用到 @ExcelProperty 的 converter 属性 针对不同枚举进行映射转换有些麻烦。http接口返回前端数据已经有枚举转换了,因此导出也复用这个枚举,这样写代码的时候字段和注解可以copy过去 不需要额外修改
  • 2.支持分页导出,为了降低数据库瞬时压力,针对数据量大的导出通常需要进行分页查询,之后将分页结果拼接进行导出,通过此方法便捷分页导出
  • 3.支持map结构的动态列,少部分情况要导出的列表是不固定的 如导出不同物料在各个仓库(每多一个仓库列表增加一列)中的库存。此时无法通过class定义表头,需要map结构动态设置列表头
  • 4.自动类型转换,数据集合的类型(数据库DTO/entity)通常与导出类不是同一个 ,工具类中会判断两者是否一致,不一致则自动进行转换
  • 5.流式编程,可以很便捷写入多个sheet
  • 6.预留扩展接口 可以便捷自定义

二、使用案例

1.示例代码

// 测试数据源
public class TestVO {// 枚举值映射转换注解 , 该注解与http返回前端的枚举值映射转换注解复用同一个, 方便copy代码@EnumView(value = TestStatusEnum.class)@ExcelProperty("状态")private Integer status;// 枚举字段名设置 codeField: 数据库存储值 默认值[code] ,nameField: 转换为对应中文名描述 默认值[name]@EnumView(value = TestTypeEnum.class, codeField = "code", nameField = "name")@ExcelProperty("类型")private Integer type;
}List<TestVO> dataList = testService.selectList(reqDTO);
PageVO<TestVO> page = testService.selectPage(TestReqDTO testVO);// http 固定列导出
ExcelExportUtil.httpResponse("文件名_http导出").addSheet("sheet1_列表导出", dataList, TestVO.class).addSheet("sheet2_列表导出(数据列自动转换为导出类)", dataList, TestExcelVO.class).addSheet("sheet3_分页导出",  reqDTO, testService::selectPage, TestVO.class).addSheet("sheet4_分页导出(数据列自动转换为导出类)",  reqDTO, testService::selectPage, TestExcelVO.class).execute();// http 动态列导出
ExcelExportUtil.httpResponse("文件名_动态列导出").addSheet("sheet名字1", reqDTO, testService::selectPage, new ExcelDynamicColumn<TestVO>() {@Overridepublic List<List<String>> head() {List<String> list = new ArrayList<>();list.add("列名一");list.add("列名二");// ...// 动态列 各个仓库名称for (String warehouseName : warehouseNameList) {list.add(warehouseName);}return List.of(list);}@Overridepublic List<Object> columnConvert(TestVO resDTO) {List<Object> list = new ArrayList<>();list.add(resDTO.getXxx());list.add(resDTO.getXxx());// ...// 动态列 各个库存数量for (Integer stockNum : resDTO.getStockNumList()) {list.add(stockNum);}return list;}}).execute();// 本地文件 固定列导出
ExcelExportUtil.file("D:\\导出测试.xlsx").addSheet("sheet1_列表导出", testExcelVOList, TestVO.class).addSheet("sheet2_列表导出(数据列自动转换为导出类)", testExcelVOList, TestExcelVO.class).addSheet("sheet3_分页导出",  reqDTO, testService::selectPage, TestVO.class).addSheet("sheet4_分页导出(数据列自动转换为导出类)",  reqDTO, testService::selectPage, TestExcelVO.class).execute();// 其他配置
ExcelExportUtil.httpResponse("文件名_固定列导出")// 设置默认 分页size, 最大读取数量限制 及对应提示.pageParams(100, 50000, "单词可导出最大数量为${maxTotalRows},当前搜索条件查询数量为{totalRows},请调整搜索条件")// 表头样式.headCellStyleConfig(config ->{config.getCell().getCellStyle().setAlignment(HorizontalAlignment.CENTER)})// 内容样式.contentCellStyleConfig(config ->{config.getCell().getCellStyle().setAlignment(HorizontalAlignment.CENTER)})// ExcelWriterBuilder配置.excelWriterConfig(config ->{config.registerWriteHandler(注册其他处理器);}).addSheet("sheet名字1",  reqDTO, testService::selectPage, TestExcelVO.class).execute();

2.效果

在这里插入图片描述


三、源码

枚举注解

/*** 枚举转换注解* Json转换 和 Excel表格导出 枚举值映射* @author caixiaopeng*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@JacksonAnnotationsInside   // http接口json转换 Jackson 库中的一个元注解(meta-annotation),它的主要作用是允许创建自定义的组合注解,将多个 Jackson 注解打包在一起复用。
@JsonSerialize(using = EnumViewSerialize.class) // 指定序列化处理器
public @interface EnumView {/*** 映射枚举* @return*/Class<? extends Enum<?>> value();/*** 枚举的code值 数据库存储值*/String codeField() default "code";/*** 枚举的name值 表格导出展示内容*/String nameField() default "name";}

动态列接口

/*** Excel导出动态列 - 接口* @param <RD> 接口数据类型* @author caixiaopeng*/
public interface ExcelDynamicColumn<RD> {/*** 表头* @return*/List<List<String>> head();/*** 列数据转换* @param resDTO* @return*/List<Object> columnConvert(RD resDTO);}

工具类

package com.xiaopeng.common.excel;import cn.hutool.core.bean.BeanUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.fastjson2.JSON;
import com.xiaopeng.common.enumUtil.EnumView;
import com.xiaopeng.common.exception.BusinessException;
import com.xiaopeng.common.exception.util.Assert;
import com.xiaopeng.common.page.BasePageRequest;
import com.xiaopeng.common.page.PageVO;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFColor;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;/*** Excel导出工具类* 基于EasyExcel 对分页接口结果集进行Excel导出* 优点:提高接口复用, 分批次查询 减轻数据库单次压力** @author xiaopeng*/
@Slf4j
public class ExcelExportUtil {// 导出类型:HttpServletResponse 方式导出private static final int EXPORT_TYPE_HTTP_RESPONSE = 1;// 导出类型:本地文件 方式导出private static final int EXPORT_TYPE_FILE = 2;// 导出类型:输出流方式private static final int EXPORT_TYPE_OUTPUT_STREAM = 3;/*** HttpServletResponse 方式导出* @param fileName  文件名* @return*/public static ExcelExportHandler httpResponse(String fileName) {ExcelExportHandler excelExportHandler = new ExcelExportHandler(EXPORT_TYPE_HTTP_RESPONSE);excelExportHandler.fileName = fileName;return excelExportHandler;}/*** 本地文件 方式导出* @param filePath 文件路径地址* @return*/public static ExcelExportHandler file(String filePath) {return file(new File(filePath));}/*** 本地文件 方式导出* @param file 文件file* @return*/public static ExcelExportHandler file(File file) {ExcelExportHandler excelExportHandler = new ExcelExportHandler(EXPORT_TYPE_FILE);String fileName = file.getName();excelExportHandler.fileName = fileName.substring(0, fileName.indexOf('.'));excelExportHandler.file = file;return excelExportHandler;}/*** 输出流方式* @param outputStream 输出流* @return*/public static ExcelExportHandler outputStream(OutputStream outputStream) {ExcelExportHandler excelExportHandler = new ExcelExportHandler(EXPORT_TYPE_OUTPUT_STREAM);excelExportHandler.outputStream = outputStream;return excelExportHandler;}public static  class DataCollectHandler<RE extends BasePageRequest, RS> {/*** sheet名字*/private String sheetName;/*** 数据源:列表数据*/private List<RS> dataList;/*** 数据源:分页请求*/private Function<RE, PageVO<RS>> pageApi;/*** 分页参数*/private RE pageParams;/*** 视图映射:class*/private Class<?> clazz;/*** 视图映射:动态列map映射*/private ExcelDynamicColumn<RS> dynamicColumn;}public static class ExcelExportHandler {/*** 导出方式*/private int exportType;/***  文件名*/private String fileName = null;/***  本地文件方式导出*/private File file = null;/***  输出流方式导出*/private OutputStream outputStream = null;/*** 分页请求 - Page size*/private int pageSize = 1000;/*** 分页请求 - 表格导出允许最大数量限制 -1不限制*/private int maxTotalRows = 10000;/*** 分页请求 - 总条数超过maxTotalRows提示语,* maxTotalRows   最大数量限制* totalRows     分页请求总条数*/private String overMaxTotalRowsLimitErrMsg = "单词可导出最大数量为${maxTotalRows},当前搜索条件查询数量为{totalRows},请调整搜索条件";/*** 数据源*/private List<DataCollectHandler> dataCollectHandlerList = new ArrayList<>();/*** excelWriterBuilder 进行操作修改*/private Consumer<ExcelWriterBuilder> excelWriterConfig;/*** 表头单元格样式处理*/private Consumer<CellWriteHandlerContext> headCellStyleConfig;/*** 内容单元格样式处理*/private Consumer<CellWriteHandlerContext> contentCellStyleConfig;public ExcelExportHandler(int exportType) {this.exportType = exportType;}/*** 设置文件名* @param fileName 文件名* @return*/public ExcelExportHandler pageSize(String fileName) {this.fileName = fileName;return this;}/*** 设置分页参数* @param pageSize          分页请求 - Page size* @return*/public ExcelExportHandler pageSize(int pageSize) {this.pageSize = pageSize;return this;}/*** 设置分页参数* @param maxTotalRows      分页请求 - 表格导出允许最大数量限制 -1不限制* @return*/public ExcelExportHandler maxTotalRows(int maxTotalRows) {this.maxTotalRows = maxTotalRows;return this;}/*** 设置分页参数* @param overMaxTotalRowsLimitErrMsg    分页请求 - 表格导出允许最大数量限制 -1不限制* @return*/public ExcelExportHandler overMaxTotalRowsLimitErrMsg(String overMaxTotalRowsLimitErrMsg) {this.overMaxTotalRowsLimitErrMsg = overMaxTotalRowsLimitErrMsg;return this;}/*** 设置分页参数* @param pageSize          分页请求 - Page size* @param maxTotalRows      分页请求 - 表格导出允许最大数量限制 -1不限制* @return*/public ExcelExportHandler pageParams(int pageSize, int maxTotalRows) {this.pageSize = pageSize;this.maxTotalRows = maxTotalRows;return this;}/*** 设置分页参数* @param maxTotalRows      分页请求 - 表格导出允许最大数量限制 -1不限制* @param overMaxTotalRowsLimitErrMsg    分页请求 - 表格导出允许最大数量限制 -1不限制* @return*/public ExcelExportHandler pageParams(int maxTotalRows, String overMaxTotalRowsLimitErrMsg) {this.maxTotalRows = maxTotalRows;this.overMaxTotalRowsLimitErrMsg = overMaxTotalRowsLimitErrMsg;return this;}/*** 设置分页参数* @param pageSize          分页请求 - Page size* @param maxTotalRows      分页请求 - 表格导出允许最大数量限制 -1不限制* @param overMaxTotalRowsLimitErrMsg    分页请求 - 表格导出允许最大数量限制 -1不限制* @return*/public ExcelExportHandler pageParams(int pageSize, int maxTotalRows, String overMaxTotalRowsLimitErrMsg) {this.pageSize = pageSize;this.maxTotalRows = maxTotalRows;this.overMaxTotalRowsLimitErrMsg = overMaxTotalRowsLimitErrMsg;return this;}/*** 添加sheet页 - 列表导出(固定列)** @param sheetName sheet页名称* @param dataList  数据源:列表数据* @param clazz     视图映射:class* @param <RS>      导出数据类型*/public <RS> ExcelExportHandler addSheet(String sheetName, List<RS> dataList, Class<?> clazz) {DataCollectHandler<BasePageRequest, RS> dataCollectHandler = new DataCollectHandler<>();dataCollectHandler.sheetName = sheetName;dataCollectHandler.dataList = dataList;dataCollectHandler.clazz = clazz;dataCollectHandlerList.add(dataCollectHandler);return this;}/*** 添加sheet页 - 列表导出(动态列)** @param sheetName     sheet页名称* @param dataList      数据源:列表数据* @param dynamicColumn 视图映射:动态列map映射* @param <RS>          导出数据类型*/public <RS> ExcelExportHandler addSheet(String sheetName, List<RS> dataList, ExcelDynamicColumn<RS> dynamicColumn) {DataCollectHandler<BasePageRequest, RS> dataCollectHandler = new DataCollectHandler<>();dataCollectHandler.sheetName = sheetName;dataCollectHandler.dataList = dataList;dataCollectHandler.dynamicColumn = dynamicColumn;dataCollectHandlerList.add(dataCollectHandler);return this;}/*** 添加sheet页 - 分页导出(固定列)** @param sheetName  sheet页名称* @param pageParams 分页参数* @param pageApi    分页接口* @param clazz      视图映射:class* @return* @param <RE>       分页请求参数类型* @param <RS>       导出数据类型*/public <RE extends BasePageRequest, RS> ExcelExportHandler addSheet(String sheetName, RE pageParams, Function<RE, PageVO<RS>> pageApi, Class<?> clazz) {DataCollectHandler<RE, RS> dataCollectHandler = new DataCollectHandler<>();dataCollectHandler.sheetName = sheetName;dataCollectHandler.pageParams = pageParams;dataCollectHandler.pageApi = pageApi;dataCollectHandler.clazz = clazz;dataCollectHandlerList.add(dataCollectHandler);return this;}/*** 添加sheet页 - 分页导出(动态列)** @param sheetName  sheet页名称* @param pageParams 分页参数* @param pageApi    分页接口* @param dynamicColumn 视图映射:动态列map映射* @return* @param <RE>       分页请求参数类型* @param <RS>       导出数据类型*/public <RE extends BasePageRequest, RS> ExcelExportHandler addSheet(String sheetName, RE pageParams, Function<RE, PageVO<RS>> pageApi, ExcelDynamicColumn<RS> dynamicColumn) {DataCollectHandler<RE, RS> dataCollectHandler = new DataCollectHandler<>();dataCollectHandler.sheetName = sheetName;dataCollectHandler.pageParams = pageParams;dataCollectHandler.pageApi = pageApi;dataCollectHandler.dynamicColumn = dynamicColumn;dataCollectHandlerList.add(dataCollectHandler);return this;}/*** 表头单元格样式处理* @param headCellStyleConfig* @return*/public ExcelExportHandler headCellStyleConfig(Consumer<CellWriteHandlerContext> headCellStyleConfig) {this.headCellStyleConfig = headCellStyleConfig;return this;}/*** 内容单元格样式处理* @param contentCellStyleConfig* @return*/public ExcelExportHandler contentCellStyleConfig(Consumer<CellWriteHandlerContext> contentCellStyleConfig) {this.contentCellStyleConfig = contentCellStyleConfig;return this;}/*** 进行操作配置修改* @param excelWriterConfig* @return*/public ExcelExportHandler excelWriterConfig(Consumer<ExcelWriterBuilder> excelWriterConfig) {this.excelWriterConfig = excelWriterConfig;return this;}/*** 完善ExcelWriterBuilder信息 并构建ExcelWriter*/private ExcelWriter getExcelWriter(ExcelWriterBuilder excelWriterBuilder) {// 自定义样式处理excelWriterBuilder.registerWriteHandler(new StyleWriteHandler(headCellStyleConfig, contentCellStyleConfig));// 枚举值映射转换excelWriterBuilder.registerWriteHandler(new ExcelEnumWriteHandler());// 如果存在自定义配置 进行配置if (Objects.nonNull(excelWriterConfig)) {excelWriterConfig.accept(excelWriterBuilder);}return excelWriterBuilder.build();}/*** 处理HttpServletResponse导出*/private void handleHttpResponse() {ServletRequestAttributes servletRequestAttributes =  (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();Assert.notNull(servletRequestAttributes, "HttpServletResponse获取失败");HttpServletResponse response = servletRequestAttributes.getResponse();try {response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");// 这里URLEncoder.encode可以防止中文乱码String fileNameEncode = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");response.setHeader("Content-disposition", "attachment;filename*=" + fileNameEncode + ".xlsx");// 这里需要设置不关闭流ExcelWriterBuilder excelWriterBuilder = EasyExcel.write(response.getOutputStream()).autoCloseStream(Boolean.FALSE);ExcelWriter excelWriter = getExcelWriter(excelWriterBuilder);// 写入数据this.handleDataWriting(excelWriter);} catch (Exception e) {log.error("文件导出异常 文件类型:" + fileName, e);try {// 重置responseresponse.reset();response.setContentType("application/json");response.setCharacterEncoding("utf-8");Map<String, Object> map = new HashMap<>();map.put("code", 500);map.put("success", false);map.put("message", e.getMessage());response.getWriter().println(JSON.toJSONString(map));} catch (IOException ex) {throw new RuntimeException(ex);}}}/*** 本地文件 方式导出*/private void handleFile() {ExcelWriterBuilder excelWriterBuilder = EasyExcel.write(file);ExcelWriter excelWriter = getExcelWriter(excelWriterBuilder);this.handleDataWriting(excelWriter);}/*** 输出流方式导出*/private void handleOutputStream() {ExcelWriterBuilder excelWriterBuilder = EasyExcel.write(outputStream);ExcelWriter excelWriter = getExcelWriter(excelWriterBuilder);this.handleDataWriting(excelWriter);}/*** 数据写入* @param excelWriter*/private void handleDataWriting(ExcelWriter excelWriter) {int sheetNo = 0;try {for (; sheetNo < dataCollectHandlerList.size(); sheetNo++) {DataCollectHandler dataCollectHandler = dataCollectHandlerList.get(sheetNo);WriteSheet writeSheet;if (Objects.nonNull(dataCollectHandler.clazz)) {// 固定列 class定义表头writeSheet = EasyExcel.writerSheet(sheetNo, dataCollectHandler.sheetName).head(dataCollectHandler.clazz).build();}  else if (Objects.nonNull(dataCollectHandler.dynamicColumn)) {// 动态列 map定义表头writeSheet = EasyExcel.writerSheet(sheetNo, dataCollectHandler.sheetName).head(dataCollectHandler.dynamicColumn.head()).build();} else {throw BusinessException.exception("缺失视图映射");}if (Objects.nonNull(dataCollectHandler.dataList)) {// 列表数据直接导出writeSheetData(excelWriter, writeSheet,dataCollectHandler.dataList, dataCollectHandler.clazz, dataCollectHandler.dynamicColumn);} else {// 分页请求 分批到处int currentPageNum = 1;PageVO page = null;do {// 获取分页数据page = queryPage(dataCollectHandler.pageApi, dataCollectHandler.pageParams, currentPageNum, pageSize);// 加油最大导出数量verifyPageTotalNum(page.getTotalRows());// 写入sheetwriteSheetData(excelWriter, writeSheet, page.getRoot(), dataCollectHandler.clazz, dataCollectHandler.dynamicColumn);currentPageNum++;} while (currentPageNum <= page.getTotalPages());}}} catch (Exception e) {e.printStackTrace();throw BusinessException.exception("sheet-{} 导出异常 {}", sheetNo, e.getMessage());}excelWriter.finish();}/*** 请求服务层接口 返回分页page** @param pageApi           请求接口-返回总数和list* @param reqDTO        分页请求入参* @param pageNum       分页第几页* @param pageSize      每次请求分页数量*/private <RE extends BasePageRequest, RS> PageVO<RS> queryPage(Function<RE, PageVO<RS>> pageApi, RE reqDTO, int pageNum, int pageSize) {reqDTO.setPageNum(pageNum);reqDTO.setPageSize(pageSize);reqDTO.setIsCount(pageNum == 1);return pageApi.apply(reqDTO);}/*** 校验导出条数是否超过最大数量限制* @param totalRows* @param <RS>*/private <RS> void verifyPageTotalNum(Long totalRows) {// -1不限制if (maxTotalRows == -1) {return;}Assert.notNull(totalRows, "page总条数为空");if (totalRows > maxTotalRows) {overMaxTotalRowsLimitErrMsg = overMaxTotalRowsLimitErrMsg.replace("${maxTotalRows}", String.valueOf(maxTotalRows));overMaxTotalRowsLimitErrMsg = overMaxTotalRowsLimitErrMsg.replace("${totalRows}", String.valueOf(totalRows));throw BusinessException.exception(overMaxTotalRowsLimitErrMsg);}}/*** 写入数据到sheet* @param excelWriter* @param writeSheet* @param dataList* @param clazz* @param dynamicColumn* @param <RE>* @param <RS>*/private <RE extends BasePageRequest, RS> void writeSheetData(ExcelWriter excelWriter, WriteSheet writeSheet, List<RS> dataList,  Class<?> clazz,  ExcelDynamicColumn<RS> dynamicColumn) {if (CollectionUtils.isEmpty(dataList)) {return;}if (Objects.nonNull(clazz)) {List<?> excelVOList = dataList;// 如果数据集合和excel导出类不是同一个 则进行转换if (dataList.getFirst().getClass() != clazz) {excelVOList = BeanUtil.copyToList(dataList, clazz);}excelWriter.write(excelVOList, writeSheet);} else if (Objects.nonNull(dynamicColumn)) {List<List<Object>> columnDataList = new ArrayList<>(dataList.size());for (RS res : dataList) {columnDataList.add(dynamicColumn.columnConvert(res));}excelWriter.write(columnDataList, writeSheet);} else {throw BusinessException.exception("缺失视图映射");}}/*** 执行导出*/public void execute() {if (exportType == EXPORT_TYPE_HTTP_RESPONSE) {// HttpServletResponse 方式导出this.handleHttpResponse();} else if (exportType == EXPORT_TYPE_FILE) {// 本地文件 方式导出this.handleFile();}  else if (exportType == EXPORT_TYPE_OUTPUT_STREAM) {// 输出流方式导出this.handleOutputStream();} else {throw new RuntimeException("导出类型无效");}}/*** 添加sheet页 并执行导出 (用于下载导入模板)* @param clazz     视图映射:class*/public <RS> void execute(Class<?> clazz) {this.addSheet(fileName, Collections.emptyList(), clazz);execute();}/*** 添加sheet页 并执行导出* @param dataList  数据源:列表数据* @param clazz     视图映射:class*/public <RS> void execute(List<RS> dataList, Class<?> clazz) {this.addSheet(fileName, dataList, clazz);execute();}/*** 添加sheet页 并执行导出** @param dataList      数据源:列表数据* @param dynamicColumn 视图映射:动态列map映射* @param <RS>          导出数据类型*/public <RS> void execute( List<RS> dataList, ExcelDynamicColumn<RS> dynamicColumn) {this.addSheet(fileName, dataList, dynamicColumn);execute();}/*** 添加sheet页 并执行导出** @param pageParams 分页参数* @param pageApi    分页接口* @param clazz      视图映射:class* @return* @param <RE>       分页请求参数类型* @param <RS>       导出数据类型*/public <RE extends BasePageRequest, RS> void execute(RE pageParams, Function<RE, PageVO<RS>> pageApi, Class<?> clazz) {this.addSheet(fileName, pageParams, pageApi, clazz);execute();}/*** 添加sheet页 - 分页导出(动态列)** @param pageParams 分页参数* @param pageApi    分页接口* @param dynamicColumn 视图映射:动态列map映射* @return* @param <RE>       分页请求参数类型* @param <RS>       导出数据类型*/public <RE extends BasePageRequest, RS> void execute(RE pageParams, Function<RE, PageVO<RS>> pageApi, ExcelDynamicColumn<RS> dynamicColumn) {this.addSheet(fileName, pageParams, pageApi, dynamicColumn);execute();}}/*** Excel 自定义样式处理拦截器*/public static class StyleWriteHandler implements CellWriteHandler {/*** 表头单元格样式处理*/private Consumer<CellWriteHandlerContext> headCellStyleConfig;/*** 内容单元格样式处理*/private Consumer<CellWriteHandlerContext> contentCellStyleConfig;public StyleWriteHandler() { }public StyleWriteHandler(Consumer<CellWriteHandlerContext> headCellStyleConfig, Consumer<CellWriteHandlerContext> contentCellStyleConfig) {this.headCellStyleConfig = headCellStyleConfig;this.contentCellStyleConfig = contentCellStyleConfig;}@Overridepublic void afterCellDispose(CellWriteHandlerContext context) {// 该方法在接口中最后执行,可对样式进行最终修改// 参考:HorizontalCellStyleStrategyif (context.getHead() == null) {return;}if (context.getHead()) {setHeadCellStyle(context);} else {setContentCellStyle(context);}}/*** 处理表头样式* @param context*/private void setHeadCellStyle(CellWriteHandlerContext context) {Cell cell = context.getCell();Head headData = context.getHeadData();Sheet sheet = context.getWriteSheetHolder().getSheet();Workbook workbook = context.getWriteWorkbookHolder().getWorkbook();CellStyle cellStyle = workbook.createCellStyle();cellStyle.cloneStyleFrom(cell.getCellStyle());// 设置行高Row row = cell.getRow();row.setHeightInPoints(24);  // 设置高度多少磅(Point)// 设置列宽Field field = headData.getField();ColumnWidth columnWidth = field.getAnnotation(ColumnWidth.class);if (columnWidth == null) {sheet.setColumnWidth(cell.getColumnIndex(), Math.min((cell.getStringCellValue().getBytes().length + 3) * 256, 20 * 256));}// 冻结列 固定展示sheet.createFreezePane(0, 1);// 设置表头rgb背颜色if (cellStyle instanceof XSSFCellStyle xssfCellColorStyle) {byte[] rgb = new byte[]{(byte) 24, (byte) 112, (byte) 255};xssfCellColorStyle.setFillForegroundColor(new XSSFColor(rgb, null));// 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUNDcellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);}// 设置水平对齐方式为居中对齐cellStyle.setAlignment(HorizontalAlignment.CENTER);// 设置垂直对齐方式为居中对齐cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);// 边框样式cellStyle.setLeftBorderColor(IndexedColors.WHITE.getIndex());cellStyle.setRightBorderColor(IndexedColors.WHITE.getIndex());cellStyle.setBorderLeft(BorderStyle.THIN);cellStyle.setBorderRight(BorderStyle.THIN);cellStyle.setBorderTop(BorderStyle.THIN);cellStyle.setBorderBottom(BorderStyle.THIN);// 设置字体样式Font font = workbook.createFont();font.setFontName("微软雅黑");
//            font.setBold(true);font.setFontHeightInPoints((short) 11);font.setColor(IndexedColors.WHITE.getIndex());cellStyle.setFont(font);cell.setCellStyle(cellStyle);if(Objects.nonNull(headCellStyleConfig)) {headCellStyleConfig.accept(context);}// 这里要把 WriteCellData的样式清空, 不然后面还有一个拦截器 FillStyleCellWriteHandler 默认会将 WriteCellStyle 设置到// cell里面去 会导致自己设置的不一样(很关键)context.getFirstCellData().setWriteCellStyle(null);}/*** 处理数据内容样式* @param context*/private void setContentCellStyle(CellWriteHandlerContext context) {Cell cell = context.getCell();Workbook workbook = context.getWriteWorkbookHolder().getWorkbook();CellStyle cellStyle = workbook.createCellStyle();// 设置行高Row row = cell.getRow();row.setHeightInPoints(18);  // 设置高度多少磅(Point)// 设置水平对齐方式为居中对齐cellStyle.setAlignment(HorizontalAlignment.CENTER);// 设置垂直对齐方式为居中对齐cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);// 设置字体样式Font font = workbook.createFont();font.setFontName("微软雅黑");// font.setBold(true);font.setFontHeightInPoints((short) 10);// font.setColor(IndexedColors.WHITE.getIndex());cellStyle.setFont(font);cell.setCellStyle(cellStyle);if(Objects.nonNull(contentCellStyleConfig)) {contentCellStyleConfig.accept(context);}// 这里要把 WriteCellData的样式清空, 不然后面还有一个拦截器 FillStyleCellWriteHandler 默认会将 WriteCellStyle 设置到// cell里面去 会导致自己设置的不一样(很关键)context.getFirstCellData().setWriteCellStyle(null);}}/*** Excel 枚举值映射转换处理拦截器*/public static class ExcelEnumWriteHandler implements CellWriteHandler {@Overridepublic void afterCellDispose(CellWriteHandlerContext context) {// beforeCellCreate,afterCellCreate 对数据的修改会给后续逻辑覆盖// 参考:ExcelWriteAddExecutor.addJavaObjectToExcel、 AbstractExcelWriteExecutor.converterAndSet// 该方法在接口中最后执行,可对数据进行最终修改if (context.getHead() == null || context.getHead()) {// 只处理数据return;}ExcelContentProperty excelContentProperty = context.getExcelContentProperty();Field field = excelContentProperty.getField();// 以class固定列方式的才执行, map形式非class的动态类不执行if (Objects.nonNull(field)) {EnumView excelView = field.getAnnotation(EnumView.class);// 对ExcelView字段进行枚举值翻译if (Objects.nonNull(excelView)) {// 传入的原始数据Object originalValue = context.getOriginalValue();if (Objects.nonNull(originalValue)) {// 获取枚举映射mapMap<Object, String> enumConvertMap = this.getEnumConvertMap(field, excelView);// 修改单元格数据String newValue = enumConvertMap.get(originalValue);if (Objects.nonNull(newValue)) {context.getCell().setCellValue(newValue);}}}}}// 字段对应的枚举映射转换map 缓存private final Map<String, Map<Object, String>> fieldEnumConvertCacheMap = new HashMap<>();/*** 获取字段对应的枚举映射转换map* @param field* @param excelView* @return*/private Map<Object, String> getEnumConvertMap(Field field, EnumView excelView) {// class模板类名String className = field.getDeclaringClass().getName();String cacheKey = className + ":" + field.getName();try {if (fieldEnumConvertCacheMap.containsKey(cacheKey)) {return fieldEnumConvertCacheMap.get(cacheKey);}Map<Object, String> enumConvertMap = this.getEnumConvertMap(excelView);fieldEnumConvertCacheMap.put(cacheKey, enumConvertMap);return enumConvertMap;} catch (Exception e) {log.error("Excel导出-获取枚举转换异常 映射字段:{} 异常;{}", cacheKey, e.getMessage(), e);throw new RuntimeException(e);}}/*** 获取对应的枚举映射转换map* @param excelView* @return* @throws Exception*/private Map<Object, String> getEnumConvertMap(EnumView excelView) throws Exception {Class<? extends Enum<?>> enumClass = excelView.value();Map<Object, String> result = new HashMap<>();Enum<?>[] enumConstants = enumClass.getEnumConstants();Field codeField = getField(enumClass, excelView.codeField());Field nameField = getField(enumClass, excelView.nameField());for (Enum<?> enumItem : enumConstants) {Object code = codeField.get(enumItem);;String name = (String) nameField.get(enumItem);;if (Objects.nonNull(code) && Objects.nonNull(name)) {result.put(code, name);}}return result;}/*** 获取class字段* @param clazz* @param fieldName* @return*/private Field getField(Class clazz, String fieldName){try {Field field =  clazz.getDeclaredField(fieldName);// 设置可访问性field.setAccessible(true);return field;} catch (NoSuchFieldException e) {throw new RuntimeException(clazz.getName() + "不存在字段" + fieldName);}}}}
http://www.xdnf.cn/news/1063315.html

相关文章:

  • GitHub Copilot快捷键
  • 缓存与加速技术实践-Kafka消息队列
  • 腾讯云IM即时通讯:开启实时通信新时代
  • Python中字符串常用的操作方法
  • Linux TCP/IP协议栈中的TCP输入处理:net/ipv4/tcp_input.c解析
  • 学习C++、QT---03(C++的输入输出、C++的基本数据类型介绍)
  • AI与SEO关键词协同进化
  • IEC61850 通信协议测试验证方法详解
  • 解锁K-近邻算法:数据挖掘的秘密武器
  • 华为云Flexus+DeepSeek征文 | 基于Flexus X实例的金融AI Agent开发:智能风控与交易决策系统
  • 【AI论文】扩散二元性
  • 面试题-定义一个函数入参数是any类型,返回值是string类型,如何写出这个函数,代码示例
  • ncu学习笔记01——合并访存
  • 系统化的Node.js服务器搭建攻略
  • 将Python的JSON字符串转换为JSON
  • UE5 游戏模板 —— FirstShootGame
  • Docker简单介绍与使用以及下载对应镜像(项目前置)
  • 【软考高级系统架构论文】论湖仓一体架构及其应用
  • RNN工作原理和架构
  • Python的6万张图像数据集CIFAR-10和CIFAR-100说明
  • Redis哨兵模式的学习(三)
  • STM32F103_LL库+寄存器学习笔记12.3 - 串口DMA高效收发实战3:支持多实例化的版本
  • 【24】二维码数据集(有v5/v8模型)/YOLO二维码检测
  • 项目拓展-Spring实现策略类统一管理
  • 【Elasticsearch】脚本(Script)
  • Duende Identity Server学习之一:认证服务器及一个Oidc/OAuth认证、用于Machine 2 Machine的客户端
  • 零基础学习Redis(12) -- Java连接redis服务器
  • 跟着AI学习C# Day29
  • 【LeetCode#第198题】打家劫舍(一维dp)
  • 【论文笔记】【强化微调】T-GRPO:对视频数据进行强化微调