我們都知道,一個xls表格的最大存儲的容量是65535條數據。如果大於這個量就會報錯,然后現實中往往需要幾十萬的下載,那么如何解決這個問題,今天我們就從兩種玩法,開始,第一種,。就是下載量小於65535的時候,
廢話不多說,直接擼代碼
一:導出功能條數小於65535的時候,可以直接使用
依賴包,自己下載
<!-- 導出用-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
<!-- alibaba easyexcel-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>1.1.2-beta5</version>
</dependency>
工具類:
POIExportUtils:
package com.hbg.dms.util; import cn.hutool.poi.excel.ExcelUtil; import cn.hutool.poi.excel.ExcelWriter; import com.hbg.dms.annotations.Excel; import com.hbg.dms.exception.DmsException; import lombok.extern.slf4j.Slf4j; import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import java.io.ByteArrayOutputStream; import java.io.OutputStream; import java.lang.reflect.Method; import java.util.List; import java.util.Map; import java.util.TreeMap; /** * @author huojg.tu * @version 1.0 * @date 2020/9/10 13:07 */ @Slf4j public class POIExportUtils { public static <T> ByteArrayOutputStream export(Class<T> objClass, List<T> dataList) { try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()){ Class excelClass = Class.forName(objClass.toString().substring(6)); Method[] methods = excelClass.getMethods(); Map<Integer, String> mapCol = new TreeMap<>(); Map<Integer, String> mapMethod = new TreeMap<>(); for (Method method : methods) { Excel excel = method.getAnnotation(Excel.class); if (excel != null) { mapCol.put(excel.order(), excel.colName()); mapMethod.put(excel.order(), method.getName()); } } HSSFWorkbook wb = new HSSFWorkbook(); if(dataList.size()>0) { poiBuildBody(poiBuildHead(wb, "sheet1", mapCol), excelClass, mapMethod, dataList); wb.write(outputStream); } return outputStream; } catch (Exception e) { log.error("導出Excel異常", e); throw new DmsException("導出失敗"); } } public static <T> ByteArrayOutputStream export1(Class<T> objClass, List<T> dataList) { try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { Method[] methods = objClass.getMethods(); Map<Integer, String> mapCol = new TreeMap<>(); Map<Integer, String> mapMethod = new TreeMap<>(); for (Method method : methods) { Excel excel = method.getAnnotation(Excel.class); if (excel != null) { mapCol.put(excel.order(), excel.colName()); mapMethod.put(excel.order(), method.getName()); } } // 通過工具類創建writer,默認創建xls格式 ExcelWriter writer = ExcelUtil.getBigWriter(); //創建xlsx格式的 //ExcelWriter writer = ExcelUtil.getWriter(true); // 一次性寫出內容,使用默認樣式,強制輸出標題 writer.writeHeadRow(mapCol.values()); writer.write(dataList); //out為OutputStream,需要寫出到的目標流 writer.flush(outputStream); // 關閉writer,釋放內存 writer.close(); return outputStream; } catch (Exception e) { log.error("導出Excel異常", e); throw new DmsException("導出失敗"); } } public static HSSFSheet poiBuildHead(HSSFWorkbook wb, String sheetName, Map<Integer, String> mapCol) { HSSFSheet sheet01 = wb.createSheet(sheetName); HSSFRow row = sheet01.createRow(0); HSSFCell cell; int i = 0; for (Map.Entry<Integer, String> entry : mapCol.entrySet()) { cell = row.createCell(i++); cell.setCellValue(entry.getValue()); } return sheet01; } public static <T> void poiBuildBody(HSSFSheet sheet01, Class<T> excelClass, Map<Integer, String> mapMethod, List<T> dataList) throws Exception { HSSFRow r = null; HSSFCell c = null; if (dataList != null && dataList.size() > 0) { for (int i = 0; i < dataList.size(); i++) { r = sheet01.createRow(i + 1); //r.setHeightInPoints(25); int j = 0; for (Map.Entry<Integer, String> entry : mapMethod.entrySet()) { c = r.createCell(j++); Object obj = excelClass.getDeclaredMethod(entry.getValue()).invoke(dataList.get(i)); c.setCellValue(obj == null ? "" : obj + ""); } } } } }
如何使用:
首頁根據你需要的傳入分頁參數
然后根據mybaties-plus 中分頁查詢出你需要的list ,
然后調用上面的工具類,直接使用,導出你需要的電子表格xls的格式文件。
request.setPageNo(1); request.setPageSize(1000000000); IPage<AgentMemberRes> agentList = this.getAgentList(request); List<AgentExportMember> dataList = BeanCopyUtil.copyList(agentList.getRecords(), AgentExportMember.class); POIExportUtils.export(AgentExportMember.class, dataList, response, "代理列表" + DateUtil.formatDate(new Date()));
二:如果下載的量大於65535條數的時候,我們玩的思想:
用spring 的監聽器來調用,和執行程序 ,然后把文件的內容存儲到OSS阿里雲上面生成zip壓縮模式,然后在數據庫把下載鏈接放到數據庫中,供下載中心使用
因此我們需要創建一個監聽器模式:
import com.hbg.dms.model.bo.ExportBO; import com.hbg.dms.model.dto.base.BasePageRequest; import org.springframework.context.ApplicationEvent; /** * 導出事件 * @author PC */ public class ExportEvent<Request extends BasePageRequest, Response, ExportResp> extends ApplicationEvent { private final ExportBO<Request, Response, ExportResp> exportBO; public ExportEvent(Object source) { super(source); exportBO= (ExportBO)source; } public ExportBO<Request, Response, ExportResp> getExportBO() { return exportBO; } }
核心代碼:這個就是我們spirng監聽器里面執行的我們大數據量出來模式
package com.hbg.dms.listener; import cn.hutool.core.date.DatePattern; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.hbg.dms.constant.UploadPathConstants; import com.hbg.dms.controller.rpc.FileDownloadRpc; import com.hbg.dms.enums.OssFileTypeEnum; import com.hbg.dms.model.bo.ExportBO; import com.hbg.dms.model.dto.base.BasePageRequest; import com.hbg.dms.model.dto.base.JsonResult; import com.hbg.dms.model.request.FileDownloadAddRequest; import com.hbg.dms.model.request.FileDownloadUpdateRequest; import com.hbg.dms.util.AliOssUtil; import com.hbg.dms.util.BeanCopyUtil; import com.hbg.dms.util.POIExportUtils; import lombok.extern.slf4j.Slf4j; import org.apache.poi.ss.usermodel.Workbook; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; /** * 歸檔監聽器 * @author huojg */ @Slf4j @Component public class ExportListener<Request extends BasePageRequest,Response, ExportResp> { private final Logger logger = LoggerFactory.getLogger(ExportListener.class); @Resource private FileDownloadRpc fileDownloadRpc; @Resource private AliOssUtil aliOssUtil; @EventListener @Async("taskExecutor") public void onExportEvent(ExportEvent<Request, Response, ExportResp> event) { ExportBO<Request, Response, ExportResp> exportBO = event.getExportBO(); //文件待下載,生成記錄到數據庫 FileDownloadAddRequest request = new FileDownloadAddRequest(); request.setContentDesc(exportBO.getFileName()); // 文件格式 1 excel; 2 :zip if (exportBO.getCompress()) { request.setFileFormat(2); } else { request.setFileFormat(1); } request.setBackUserId(exportBO.getBackUserId()); request.setBackUserName(exportBO.getBackUserName()); // 文件來源 hbg:惠啵購, dms:經銷商管理系統 request.setSourceFrom("dms"); FileDownloadUpdateRequest updateRequest = new FileDownloadUpdateRequest(); //try(ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ZipOutputStream zipOutputStream = null; try { zipOutputStream = new ZipOutputStream(outputStream); //調用HBG服務保存記錄 JsonResult<Integer> saveResult = fileDownloadRpc.save(request); updateRequest.setId(saveResult.getData()); //獲取數據,同實例 串行導出,避免內存占用過大 synchronized (this) { byte[] byteArr = getDataList(exportBO, zipOutputStream); try { //需要先關閉zip,后關閉outputStream zipOutputStream.flush(); zipOutputStream.close(); outputStream.close(); } catch (IOException e) { logger.error("FileListener.onFileEvent" + e.getMessage(), e); } String fileName; if (exportBO.getCompress()) { byteArr = outputStream.toByteArray(); fileName = exportBO.getFileName() + "_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern(DatePattern.PURE_DATETIME_PATTERN)) + ".zip"; } else { fileName = exportBO.getFileName() + "_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern(DatePattern.PURE_DATETIME_PATTERN)) + exportBO.getSuffix(); } String downloadUrl = aliOssUtil.uploadFile(OssFileTypeEnum.DOCUMENT, byteArr, exportBO.getSubPath(), fileName); updateRequest.setDownloadUrl(downloadUrl); updateRequest.setFileStatus(1); } //調用HBG服務 更新狀態 fileDownloadRpc.updateStatus(updateRequest); } catch (Exception e) { logger.error("ExportListener.onExportEvent" + e.getMessage(), e); if (updateRequest.getId() != null) { updateRequest.setFileStatus(2); fileDownloadRpc.updateStatus(updateRequest); } } } /** * 抽象數據獲取方法 , 並且寫入到流 */ private byte[] getDataList(ExportBO<Request, Response, ExportResp> exportBO, ZipOutputStream zipOutputStream) throws IOException { if (exportBO.getBatchSize() == null || exportBO.getBatchSize() <= 0) { exportBO.setBatchSize(10_000); } //一次最多查詢50000條 if (exportBO.getBatchSize() > 50_000) { exportBO.setBatchSize(50_000); } exportBO.getParamRequest().setPageSize(exportBO.getBatchSize()); IPage<Response> dataPage; int index = 0; //是否應該繼續 boolean shouldContinue; do { if (exportBO.getMaxPage() != null && exportBO.getMaxPage() < 1) { logger.error("用戶設置的最大頁數小於1,不查詢數據"); break; } exportBO.getParamRequest().setPageNo(++index); //分頁查詢 dataPage = exportBO.getDataSupplierFunc().apply(exportBO.getParamRequest()); if (dataPage.getRecords().size() > 0) { List<ExportResp> exportResponseList = BeanCopyUtil.copyList(dataPage.getRecords(), exportBO.getExportRespClass()); ByteArrayOutputStream outputStream = POIExportUtils.export(exportBO.getExportRespClass(), exportResponseList); byte[] byteArray = outputStream.toByteArray(); //選擇不壓縮,則只查詢一次 if (!exportBO.getCompress()) { return byteArray; } zipAppend(byteArray, exportBO.getFileName(), zipOutputStream, index); } shouldContinue = dataPage.getPages() > dataPage.getCurrent() && (exportBO.getMaxPage() == null || exportBO.getMaxPage() > dataPage.getCurrent()); } while(shouldContinue); return null; } //壓縮 protected static void zipAppend(byte[] byteArray, String zipEntityName, ZipOutputStream zipStream, int index) throws IOException { if(byteArray!=null && byteArray.length > 0){ ZipEntry zipEntry = new ZipEntry(new String(zipEntityName.getBytes(), StandardCharsets.UTF_8) + "_" +index +".xls"); zipStream.putNextEntry(zipEntry); zipStream.write(byteArray); zipStream.closeEntry(); } } }
如何使用:
ExportBO<MemberSearchReq, AgentMemberRes, AgentMemberExportRes> exportBO = new ExportBO<>(); exportBO.setParamRequest(request); exportBO.setDataSupplierFunc(this::getAgentList); exportBO.setBatchSize(50000); exportBO.setExportRespClass(AgentMemberExportRes.class); exportBO.setFileName("代理列表"); exportBO.setSubPath(UploadPathConstants.MEMBER_LIST); //exportBO.setCompress(true); //exportBO.setSuffix(".xls"); // 發布導出事件,等待異步導出 publisher.publishEvent(new ExportEvent<>(exportBO));
定義實體類:
package com.hbg.dms.model.bo; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.api.R; import com.hbg.dms.constant.UploadPathConstants; import com.hbg.dms.model.dto.base.BasePageRequest; import io.micrometer.core.lang.Nullable; import lombok.Builder; import lombok.Getter; import lombok.Setter; import org.apache.poi.ss.formula.functions.T; import java.util.List; import java.util.function.Function; /** * 抽象的導出事件對象 * @author zxf * @date 2019/11/11 14:48 */ @Getter @Setter public class ExportBO<Request extends BasePageRequest, Response, ExportResp> { /** * 數據來源入參參數 */ private Request paramRequest; /** * 數據來源方法:用於獲取數據 * 一般使用分頁后台管理對應的分頁接口:需要保證只有一個入參 */ private Function<Request, IPage<Response>> dataSupplierFunc; /** * 需要構建一個導出用的對象, * 程序將使用 {@link com.hbg.dms.annotations.Excel} 來獲取excel的列標題和順序 * 轉換的數據導出對象 */ private Class<ExportResp> exportRespClass; /** * 可選參數 * 批量導出,每次查詢的數據條數 * 注意:如果選擇了不壓縮,那么只會導出第一頁的數據,全部導出需要設置比較大的size * 適用於數據量小於50000的數據 * 大於5w的強制只查詢5W * */ private Integer batchSize = 10000; /** * 可選參數 * 希望導出的最大頁數 * 為空或小於1則查詢全部 */ private Integer maxPage; /** * 文件名,建議帶正確的后綴 */ private String fileName; /** * 可選參數 * 文件名后綴,帶點號 * eg: .xls * 默認 .xlsx */ private String suffix = ".xls"; /** * 可選參數 * 是否壓縮 * 默認 是 * */ private Boolean compress = true; /** * 可選參數 * 使用子路徑,會生成子路徑文件夾,作為更細的划分 * @see UploadPathConstants */ private String subPath; /** * dms獲取不到session的用戶信息,需要前端傳遞此參數 * 后台操作的用戶Id */ private Long backUserId; /** * dms獲取不到session的用戶信息,需要前端傳遞此參數 * 操作人姓名 */ private String backUserName; }
package com.hbg.dms.model.dto.base; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @ApiModel("分頁參數") public class BasePageRequest { @ApiModelProperty(value = "當前頁碼", required = true) private Integer pageNo; @ApiModelProperty(value = "每頁條數", required = true) private Integer pageSize; public int getPageNo() { if(pageNo == null || pageNo <= 0){ pageNo = 1; } return pageNo; } public int getPageSize() { if(pageSize == null || pageSize <= 0){ pageSize = 10; } return pageSize; } @ApiModelProperty(hidden = true) public <T> Page<T> getPage() { return new Page<>(getPageNo(), getPageSize()); } public void setPageNo(Integer pageNo) { this.pageNo = pageNo; } public void setPageSize(Integer pageSize) { this.pageSize = pageSize; } }
通過上面的定義實體類:
ExportBO
我們通過執行分頁的list集合。處理代碼
參數解析:
request:請求參數
this::getAgentList:查詢分頁出來的list
AgentMemberExportRes.class:導出的實體類
setSubPath:路徑名稱
publishEvent:發送通知
Excel:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Excel { /** * 列名稱 * @return */ String colName(); /** * 順序 * @return */ int order(); }
導出的實體類上面,這樣寫:
@Excel(colName = "上級id", order = 1) public Integer getSupperId() { return supperId; }
這樣可以導出,幾十萬的是數據量。
帶分頁--大數據量導出的功能就此完成--你們的精華,就是,監聽器實現的代碼。同學看不懂的,可以聯系我要源碼。收費10元
