实时下载导出报表
====================================================================================
1.修改总览
1.pom.xml
<!-- 阿里巴巴easyexcel start--> <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>${easyexcel.version}</version> <exclusions> <exclusion> <groupId>cglib</groupId> <artifactId>cglib</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.2</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>${poi.version}</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>${poi.version}</version> </dependency> <!-- 阿里巴巴easyexcel end-->
2.controller
package com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.controller; import com.alibaba.fastjson.JSON; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.dto.ReportDTO; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.enums.ReportEnum; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.exception.ReportException; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.query.DUQuery; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.service.ReportService; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; import java.io.File; import java.io.FileInputStream; import java.io.OutputStream; import java.net.URLEncoder; @Slf4j @Validated @RestController @RequestMapping("/xlsx/downloadAndUpload") public class XlsxDownloadAndUploadController { @Resource private ReportService reportService; /** * 实时下载 xlsx文件 * 将根据查询条件实时查询到的数据 通过 xlsx文件导出 * @param query 导出查询条件 * @return */ @RequestMapping(value = "/rtDownload", method = {RequestMethod.GET}) public void rtDownload(HttpServletResponse response, @Valid DUQuery query) { OutputStream outputStream = null; FileInputStream inputStream =null; File file = null; try { ReportDTO dto = new ReportDTO(); dto.setReportEnum(ReportEnum.RT_REPORT); dto.setRequestParams(JSON.toJSONString(query)); file = reportService.generateReport(dto); response.setContentType("mutipart/form-data"); response.setCharacterEncoding("utf-8"); response.setHeader("Content-disposition","attachment;filename="+ URLEncoder.encode(file.getName(), "utf-8")); outputStream = response.getOutputStream(); inputStream = new FileInputStream(file); byte[] b = new byte[1024]; int length = 0; while ((length = inputStream.read(b)) != -1) { outputStream.write(b, 0, length); } outputStream.flush(); } catch (Exception e) { log.error("导出业务报表发生错误:", e); throw new ReportException("导出业务报表发生错误!"); }finally{ reportService.close(outputStream,inputStream); reportService.delFile(file); } } }
3.报表service
package com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.service.impl; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.ExcelWriter; import com.alibaba.excel.write.metadata.WriteSheet; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.context.ReportGenerateContext; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.dto.ReportDTO; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.enums.ReportEnum; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.exception.ReportException; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.handler.AbstractReportHandler; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.service.ReportService; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.utils.XlsxCellWidthUtil; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.utils.XlsxCellWriteUtil; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.utils.XlsxHeadUtil; import com.sxd.swapping.utils.serialNum.SerialNumHelper; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Properties; /** * 业务报表Service * * 报表导出的 核心逻辑在这里 * * 1.边读边写 解决FullGC问题 * 2.集成多种业务报表 不同的报表类型可以在ReportEnum中以不同枚举定义 定义入参class 出参class 报表名称 等关键信息 * 3.单元格支持超过15位长的纯数字串不会显示成E+问题 * 4.generateReport() 生成临时File,支持【实时导出】 和 【上传远程服务器得到可直接下载的URL 暂未实现】 */ @Slf4j @Service public class ReportServiceImpl implements ReportService { @Resource private ReportGenerateContext reportGenerateContext; /** * 生成报表临时文件 * @param reportDTO * @return */ @Override public File generateReport(ReportDTO reportDTO) { ReportEnum reportEnum = reportDTO.getReportEnum(); if (reportEnum == null) { throw new ReportException("报表类型必传!"); } if (StringUtils.isBlank(reportDTO.getRequestParams())) { throw new ReportException("报表入参数据必传!"); } //1.获取临时文件路径[需定义自定义报表枚举] String filePath = getFilePath(reportEnum.getTempFileName()); //2.初始化xlsx文件对象 [需定义自定义表头工具] ExcelWriter excelWriter = EasyExcel .write(filePath, reportEnum.getReportClass()) // .registerWriteHandler(new XlsxCellFontUtil()) //默认情况不用显式设置,除非业务有需求 // .registerWriteHandler(XlsxCellStyleUtil.getCellStyleStrategy()) //默认情况不用显式设置,除非业务有需求 .registerWriteHandler(new XlsxCellWidthUtil()) //一般情况下可以不用设置,除非表头没正常生成 .registerWriteHandler(new XlsxCellWriteUtil()) //如果导出数据中,有超过15位的数字串,则需要设置 .head(XlsxHeadUtil.getHeadByReportEnum(reportEnum)) //一般情况可以不用设置,除非表头没正常生成 .build(); WriteSheet writeSheet = EasyExcel.writerSheet(reportEnum.getDesc()).build(); reportDTO.setExcelWriter(excelWriter); reportDTO.setWriteSheet(writeSheet); //3.获取报表handler完成xlsx文件生成[需定义自定义handler并加入上下文] AbstractReportHandler reportHandler = reportGenerateContext.getReportHandler(reportEnum); reportHandler.generateReport(reportDTO); //4.关闭easyxlsx写入器 excelWriter.finish(); return new File(filePath); } /** * 根据文件名获取文件路径 * @param tempFileName * @return */ @Override public String getFilePath(String tempFileName) { Properties properties = System.getProperties(); String path = properties.getProperty("user.dir"); if (properties.getProperty("os.name").toLowerCase().contains("win")) { path += "\"; } else { path += "/"; } path += String.format(tempFileName, SerialNumHelper.generateRecordId()); log.info("DownloadServiceImpl#getFilePath={}" , path); return path; } /** * 删除临时文件 * @param file */ @Override public void delFile(File file) { if (file != null && file.exists()) { try { file.delete(); }catch (Exception e) { log.error("删除临时文件失败:", e); } } } /** * 关闭资源 * @param outputStream * @param inputStream */ @Override public void close(OutputStream outputStream, InputStream inputStream) { closeOutStream(outputStream); cloaseInputStream(inputStream); } @Override public void closeOutStream(OutputStream outputStream) { if(outputStream !=null){ try { outputStream.close(); } catch (IOException e) { log.error("关闭输出流失败:", e); } } } @Override public void cloaseInputStream(InputStream inputStream) { if(inputStream != null ){ try { inputStream.close(); } catch (IOException e) { log.error("关闭输入流失败:", e); } } } }
4.报表枚举ReportEnum
package com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.enums; import com.fasterxml.jackson.annotation.JsonCreator; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.query.DUQuery; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.vo.RTReportVO; import lombok.AllArgsConstructor; import lombok.Getter; import java.util.Arrays; import java.util.Objects; @AllArgsConstructor @Getter public enum ReportEnum { /** 枚举项 */ RT_REPORT(1, "实时导出报表", RTReportVO.class, DUQuery.class,"实时导出报表-%s.xlsx"), ; /**值*/ private Integer value; /**报表描述*/ private String desc; /**导出模板*/ private Class reportClass; /**业务入参*/ private Class requestParams; /**报表文件名称*/ private String tempFileName; @JsonCreator public static ReportEnum valueOf(Integer value) { return Arrays.stream(ReportEnum.values()) .filter(e -> Objects.equals(e.value, value)).findFirst() .orElseThrow(() -> new RuntimeException("ReportEnum value=" + value + " not exists!")); } }
5.业务枚举DUStatusEnum
package com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.enums; import com.fasterxml.jackson.annotation.JsonCreator; import lombok.AllArgsConstructor; import lombok.Getter; import java.util.Arrays; import java.util.Objects; @AllArgsConstructor @Getter public enum DUStatusEnum { WAIT(0,"等待处理"), SUCCESS(1,"成功"), FAIL(-1, "失败") ; /**值*/ private Integer value; /**报表描述*/ private String desc; @JsonCreator public static DUStatusEnum valueOf(Integer value) { return Arrays.stream(DUStatusEnum.values()) .filter(e -> Objects.equals(e.value, value)).findFirst() .orElseThrow(() -> new RuntimeException("DUStatusEnum value=" + value + " not exists!")); } }
6.报表上下文ReportGenerateContext
package com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.context; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.enums.ReportEnum; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.handler.AbstractReportHandler; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.handler.RTReportHandler; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.Map; /** * 报表生成上下文 */ @Component public class ReportGenerateContext implements ApplicationContextAware { private Map<ReportEnum, AbstractReportHandler> reportHandlerMap; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { if (reportHandlerMap == null) { reportHandlerMap = new HashMap<>(); reportHandlerMap.put(ReportEnum.RT_REPORT,applicationContext.getBean(RTReportHandler.class)); } } public AbstractReportHandler getReportHandler(ReportEnum reportEnum){ return reportHandlerMap.get(reportEnum); } }
请求实时下载:
http://localhost:9666/xlsx/downloadAndUpload/rtDownload
生成报表结果展示: