實時下載導出報表
====================================================================================
1.修改總覽
1.pom.xml

<easyexcel.version>2.2.7</easyexcel.version>
<poi.version>3.17</poi.version>
<!-- 阿里巴巴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.報表ReportService

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); } }
7.抽象報表處理器 AbstractReportHandler

package com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.handler; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.dto.ReportDTO; /** * 抽象的報表處理器 * 各種報表的 自定義的報表處理邏輯 均實現該抽象接口 * */
public abstract class AbstractReportHandler { public abstract void generateReport(ReportDTO reportDTO); }
8.實時報表處理器(上面的實現子類)RTReportHandler

package com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.handler; import com.alibaba.excel.ExcelWriter; import com.alibaba.excel.write.metadata.WriteSheet; import com.alibaba.fastjson.JSON; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.assemble.DUDB2VOAssemble; import com.sxd.swapping.mybatis.pojo.DownloadUpload; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.dto.ReportDTO; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.enums.DUStatusEnum; 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.DUDBService; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.vo.RTReportVO; import com.sxd.swapping.utils.dateTime.DateTimeHelper; import org.apache.commons.collections4.CollectionUtils; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.List; import java.util.stream.Collectors; /** * 實時報表的 處理器 * 【每一種報表 是一個單獨實現的 處理器】 * 【所以新增加一種報表 就去單獨實現 各自的處理器即可】 */ @Component public class RTReportHandler extends AbstractReportHandler{ @Resource private DUDBService dudbService; @Resource private DUDB2VOAssemble dudb2VOAssemble; @Override public void generateReport(ReportDTO reportDTO) { ExcelWriter excelWriter = reportDTO.getExcelWriter(); WriteSheet writeSheet = reportDTO.getWriteSheet(); if (excelWriter == null){ throw new ReportException("easyexcel寫入器不能為空"); } if (writeSheet == null) { throw new ReportException("sheet寫入器不能為空"); } //1.初始化入參
String requestParams = reportDTO.getRequestParams(); ReportEnum reportEnum = reportDTO.getReportEnum(); DUQuery duQuery = (DUQuery) JSON.parseObject(requestParams, reportEnum.getRequestParams()); //2.初次查詢統計總數
int total = dudbService.count(duQuery); int totalPage = total/duQuery.getPageSize() + 1; //3.分頁分批次邊讀邊寫 【防止超大數據量下FullGC】
for (int i = 1; i <= totalPage; i++) { duQuery.setStart((duQuery.getCurrentPage()-1) * duQuery.getPageSize()); List<DownloadUpload> dbResult = dudbService.query(duQuery); if (CollectionUtils.isNotEmpty(dbResult)) { List<RTReportVO> reportDTOs = dbResult.stream().map(dudb2VOAssemble::from).collect(Collectors.toList()); transferDataDesc(reportDTOs); excelWriter.write(reportDTOs,writeSheet); clearOneBatch(reportDTOs, dbResult); } } } private void clearOneBatch(List<RTReportVO> reportVOS, List<DownloadUpload> dbResult){ if (reportVOS != null) { reportVOS.clear(); } if (dbResult != null ) { dbResult.clear(); } } /** * 將數據轉化為描述性文字 * @param reportVOS */
private void transferDataDesc(List<RTReportVO> reportVOS){ if (CollectionUtils.isNotEmpty(reportVOS)) { reportVOS.forEach(i-> { if (i.getDuId() != null) { i.setDuIdDesc(String.valueOf(i.getDuId())); } if (i.getCreateTime() != null) { i.setCreateTimeDesc(DateTimeHelper.getDateTimeStr(i.getCreateTime())); } if (i.getDuStatus() != null) { i.setDuStatusDesc(DUStatusEnum.valueOf(i.getDuStatus()).getDesc()); } }); } } }
9.業務DUDBService

package com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.service.impl; import com.sxd.swapping.mybatis.pojo.DownloadUpload; import com.sxd.swapping.mybatis.dao.DUMapper; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.query.DUQuery; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.service.DUDBService; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.List; @Service public class DUDBServiceImpl implements DUDBService { @Resource private DUMapper duMapper; /** * 假設 從第一頁開始查詢 一直查詢到所有數據 * * @param query * @return
*/ @Override public List<DownloadUpload> query(DUQuery query) { return duMapper.queryDU(query); } @Override public int count(DUQuery duQuery) { return duMapper.countDU(duQuery); } }
10.mapstruct Spring支持的克隆轉化 DUDB2VOAssemble

package com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.assemble; import com.sxd.swapping.mybatis.pojo.DownloadUpload; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.vo.RTReportVO; import org.mapstruct.Mapper; /** * mapstruct * Spring支持的克隆 */ @Mapper(componentModel = "spring") public interface DUDB2VOAssemble { RTReportVO from(DownloadUpload downloadUpload); }
11.導出報表的過程數據ReportDTO

package com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.dto; import com.alibaba.excel.ExcelWriter; import com.alibaba.excel.write.metadata.WriteSheet; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.enums.ReportEnum; import lombok.Data; /** * 導出報表的 過程數據 */ @Data public class ReportDTO { /**報告類型枚舉*/
private ReportEnum reportEnum; /**序列化入參*/
private String requestParams; /**easyexcel寫入器 所有報表通用*/
private ExcelWriter excelWriter; /**sheet寫入器 所有報表通用*/
private WriteSheet writeSheet; }
12.業務查詢DUQuery 和 PageQuery

package com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.query; import lombok.Data; /** * 分頁查詢 */ @Data public abstract class PageQuery { private Integer currentPage = 1; private Integer pageSize = 10; private Integer start; }

package com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.query; import lombok.Data; import javax.validation.constraints.NotNull; /** * 上傳下載業務的 查詢條件 * DownloadAndUploadQuery */ @Data public class DUQuery extends PageQuery{ /**主鍵ID*/
// @NotNull(message = "業務ID不能為空")
private Long duId; /**狀態*/
private Integer duStatus; }
13.業務數據DUMapper

package com.sxd.swapping.mybatis.dao; import com.sxd.swapping.mybatis.pojo.DownloadUpload; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.query.DUQuery; import org.apache.ibatis.annotations.Mapper; import java.util.List; @Mapper public interface DUMapper { List<DownloadUpload> queryDU(DUQuery duQuery); int countDU(DUQuery duQuery); }

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sxd.swapping.mybatis.dao.DUMapper">
<select id="queryDU" parameterType="com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.query.DUQuery" resultType="com.sxd.swapping.mybatis.pojo.DownloadUpload"> select * from download_upload du <where>
<if test="duId != null"> AND du.du_id = #{duId} </if>
<if test="duStatus != null"> AND du.du_status = #{duStatus} </if>
</where>
<if test="start != null and pageSize != null"> LIMIT #{start}, #{pageSize} </if>
</select>
<select id="countDU" parameterType="com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.query.DUQuery" resultType="java.lang.Integer"> select count(id) from download_upload du <where>
<if test="duId != null"> AND du.du_id = #{duId} </if>
<if test="duStatus != null"> AND du.du_status = #{duStatus} </if>
</where>
</select>
</mapper>
14.POJO DownloadUpload

package com.sxd.swapping.mybatis.pojo; import lombok.Data; import java.time.LocalDateTime; /** * 數據庫表的POJO */ @Data public class DownloadUpload { /** * DB 自增ID */
private Long id; /** * 業務ID */
private Long duId; /** * 業務名稱 */
private String duName; /** * 業務狀態 枚舉值 */
private Integer duStatus; /** * 業務時間 */
private LocalDateTime createTime; }
15.業務視圖(即報表預設列對象) RTReportVO
即導出的報表 應該導出那些列,列名是什么,哪些列忽略,在此設置

package com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.vo; import com.alibaba.excel.annotation.ExcelIgnore; import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.write.style.ColumnWidth; import lombok.Data; import java.time.LocalDateTime; @Data @ColumnWidth(25) public class RTReportVO { /** * 業務ID */ @ExcelIgnore private Long duId; /** * 業務ID */ @ExcelProperty(value = "業務ID",index = 0) private String duIdDesc; /** * 業務名稱 */ @ExcelProperty(value = "業務名稱",index = 1) private String duName; /** * 業務狀態 枚舉值 */ @ExcelIgnore private Integer duStatus; /** * 業務狀態 枚舉描述 */ @ExcelProperty(value = "業務狀態",index = 2) private String duStatusDesc; /** * 業務時間 DB字段 */ @ExcelIgnore private LocalDateTime createTime; /** * 業務時間 展示字段 */ @ExcelProperty(value = "業務時間",index = 3) private String createTimeDesc; }
16.easy-excel工具類
xlsx單元格字體樣式工具類 XlsxCellFontUtil

package com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.utils; import com.alibaba.excel.metadata.CellData; import com.alibaba.excel.metadata.Head; import com.alibaba.excel.write.handler.AbstractCellWriteHandler; import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; import com.alibaba.excel.write.metadata.holder.WriteTableHolder; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.enums.DUStatusEnum; import org.apache.poi.hssf.util.HSSFColor; import org.apache.poi.ss.usermodel.*; import java.util.List; /** * 設置單元格字體樣式 * * 默認情況下 不用顯式聲明該Util 除非業務上有特殊需求 */
public class XlsxCellFontUtil extends AbstractCellWriteHandler { @Override public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { int columnIndex = cell.getColumnIndex(); // 列索引是2的時候是狀態欄列 // 這里處理邏輯即: 如果狀態是成功,則設置字體為綠色 【這里僅針對RTReport】
if (columnIndex == 2) { String stringCellValue = cell.getStringCellValue(); if (DUStatusEnum.SUCCESS.getDesc().equalsIgnoreCase(stringCellValue)) { Workbook workbook = writeSheetHolder.getSheet().getWorkbook(); CellStyle cellStyle = workbook.createCellStyle(); Font font = workbook.createFont(); font.setFontName("微軟雅黑"); // 這里設置字體高度,需要看Excel的高度乘以20,比如:如果要想在Excel看到的高度是11,那么這里設置為220
font.setFontHeight((short) 220); font.setColor(HSSFColor.HSSFColorPredefined.GREEN.getIndex()); cellStyle.setFont(font); // 設置字體對齊方式
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER); cellStyle.setAlignment(HorizontalAlignment.LEFT); cell.setCellStyle(cellStyle); } } } }
xlsx單元格樣式處理器 XlsxCellStyleUtil

package com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.utils; import com.alibaba.excel.write.metadata.style.WriteCellStyle; import com.alibaba.excel.write.metadata.style.WriteFont; import com.alibaba.excel.write.style.HorizontalCellStyleStrategy; import org.apache.poi.ss.usermodel.HorizontalAlignment; import org.apache.poi.ss.usermodel.IndexedColors; import org.apache.poi.ss.usermodel.VerticalAlignment; /** * xlsx單元格樣式處理器 * * 默認不用顯式設置 除非業務有特殊需求 */
public class XlsxCellStyleUtil { private XlsxCellStyleUtil() { throw new IllegalArgumentException(); } public static HorizontalCellStyleStrategy getCellStyleStrategy() { /*******自定義列標題和內容的樣式******/
// 頭的策略
WriteCellStyle headWriteCellStyle = new WriteCellStyle(); // 背景設置為紅色
headWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex()); WriteFont headWriteFont = new WriteFont(); // 設置標題字體大小
headWriteFont.setFontHeightInPoints((short) 11); // 設置標題字體
headWriteFont.setFontName("微軟雅黑"); // 設置標題字體是否加粗
headWriteFont.setBold(true); headWriteCellStyle.setWriteFont(headWriteFont); // 設置是否自動換行
headWriteCellStyle.setWrapped(true); // 內容的策略
WriteCellStyle contentWriteCellStyle = new WriteCellStyle(); // 設置內容字體
WriteFont contentWriteFont = new WriteFont(); // 字體大小
contentWriteFont.setFontName("微軟雅黑"); // 設置標題字體大小
contentWriteFont.setFontHeightInPoints((short) 11); contentWriteCellStyle.setWriteFont(contentWriteFont); //設置 自動換行
contentWriteCellStyle.setWrapped(false); //設置 垂直居中
contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER); //設置 水平居左
contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.LEFT); // 這個策略是 頭是頭的樣式 內容是內容的樣式 其他的策略可以自己實現
return new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle); } }
xlsx單元格寬度工具類 XlsxCellWidthUtil

package com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.utils; import com.alibaba.excel.metadata.CellData; import com.alibaba.excel.metadata.Head; import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy; import org.apache.poi.ss.usermodel.Cell; import java.util.List; /** * xlsx單元格寬度工具類 * * 一般情況下,可不一不用顯式的重寫該 方法,但 可能遇上 導出xlsx文件沒有表頭的情況,為解決這種異常,就需要顯式去重寫該方法。 */
public class XlsxCellWidthUtil extends AbstractColumnWidthStyleStrategy { @Override protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { if (isHead) { int columnWidth = 25; writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), columnWidth * 256); } } }
xlsx單元格格式工具類 XlsxCellWriteUtil

package com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.utils; import com.alibaba.excel.metadata.CellData; import com.alibaba.excel.metadata.Head; import com.alibaba.excel.write.handler.CellWriteHandler; import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; import com.alibaba.excel.write.metadata.holder.WriteTableHolder; import org.apache.poi.ss.usermodel.*; import java.util.List; /** * xlsx設置單元格格式為 文本格式工具類 * * 第一步:xlsx 設置單元格格式為 文本格式 * 但是數字超過15位以上的數字串,依舊會顯示為E+ 解決該問題,僅設置單元格格式為 文本格式還不能完全解決, * 第二步:需要 將 數值類型的字段 Long類型字段 按照String 返回,去寫入單元格,即可解決E+問題。 例如:ActivitySyncRecordDTO 類中的actId是Long類型超過15位的數字串,寫出應該用actIdDesc為String類型。 * * 【備選方案,未測試:需要配合在數字串前加上英文單引號' ,即可解決】 * */
public class XlsxCellWriteUtil implements CellWriteHandler { @Override public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) { } @Override public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { //設置單元格格式為文本
Workbook workbook = writeSheetHolder.getSheet().getWorkbook(); CellStyle cellStyle = workbook.createCellStyle(); DataFormat dataFormat = workbook.createDataFormat(); cellStyle.setDataFormat(dataFormat.getFormat("@")); cell.setCellStyle(cellStyle); } @Override public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { } @Override public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { } }
xlsx設置表頭工具類 XlsxHeadUtil

package com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.utils; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.enums.ReportEnum; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * xlsx設置表頭工具類 * * 一般情況下 不需要顯示設置該表頭工具, easeexcel的注解標注了導出表結構一般會正常生成表頭,例如RTReportVO類 * 如果發生導出的xlsx表頭不見了,處理該異常則可以通過顯式設置該表頭 完成xlsx表頭的正常生成 * */
public class XlsxHeadUtil { public static List<List<String>> getHeadByReportEnum(ReportEnum reportEnum) { List<List<String>> heads = new ArrayList<>(); switch (reportEnum) { case RT_REPORT: heads = generateRTReportHead(); break; default: break; } return heads; } private static List<List<String>> generateRTReportHead() { List<List<String>> heads = new ArrayList<>(); heads.add(Collections.singletonList("業務ID")); heads.add(Collections.singletonList("業務名稱")); heads.add(Collections.singletonList("業務狀態")); heads.add(Collections.singletonList("業務時間")); return heads; } }
17.報表自定義異常 ReportException

package com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.exception; import com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.result.DUResult; /** * 報表自定義業務報警 */
public class ReportException extends RuntimeException { private final String code; public ReportException(String message) { super(message); this.code = DUResult.ERROR_CODE; } public ReportException(String code, String message) { super(message); this.code = code; } }
18.Controller公共響應體DUResult

package com.sxd.swapping.downloadAndUpload.xlsxDownloadAndUpload.result; import lombok.Data; /** * DownloadAndUpload 公共返回對象 */ @Data public class DUResult { public static final String SUCCESS_CODE = "0000"; public static final String ERROR_CODE = "9999"; /** * 狀態碼 */
private String code; /** * 狀態碼描述 */
private String msg; /** * 響應結果 */
private Object data; private DUResult(String code, String msg) { this(code, msg, null); } private DUResult(String code, String msg, Object data) { this.code = code; this.msg = msg; this.data = data; } public static DUResult build(String code, String message) { return new DUResult(code, message); } public static DUResult build(String code, String message, Object data) { return new DUResult(code, message, data); } public static DUResult success() { return build(SUCCESS_CODE, "處理成功"); } public static DUResult success(String code, String message) { return build(code, message); } public static DUResult success(Object data) { return build(SUCCESS_CODE, "處理成功", data); } public static DUResult error() { return build(ERROR_CODE, "處理失敗"); } public static DUResult error(String message) { return error(ERROR_CODE, message); } public static DUResult error(String code, String message) { return build(code, message); } }
19.工具類
時間格式工具 DateTimeHelper

package com.sxd.swapping.utils.dateTime; import java.time.DateTimeException; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; public class DateTimeHelper { public static final String PATTERN_1 = "yyyy-MM-dd HH:mm:ss"; public static final String PATTERN_2 = "yyyyMMddHHmmssSSS"; /** * 時間類型 轉 字符串 * @param localDateTime * @return
*/
public static String getDateTimeStr(LocalDateTime localDateTime){ if (localDateTime == null) { return null; } try { DateTimeFormatter format = DateTimeFormatter.ofPattern(PATTERN_1); return localDateTime.format(format); } catch (DateTimeException ex) { ex.printStackTrace(); return null; } } }
序列號工具 SerialNumHelper

package com.sxd.swapping.utils.serialNum; import com.sxd.swapping.utils.dateTime.DateTimeHelper; import com.xiaoleilu.hutool.date.DateUtil; import java.util.Date; public class SerialNumHelper { public synchronized static String generateRecordId() { String daystr = DateUtil.format(new Date(), DateTimeHelper.PATTERN_2); //TODO redis自增
return daystr; } }
20.application.properties配置文件

server.port=9666 spring.jackson.time-zone=GMT+8 #spring boot2.0 限制文件上傳大小【spring boot默認1MB】 spring.servlet.multipart.max-file-size=90MB spring.servlet.multipart.max-request-size=100MB #datasource spring.datasource.continue-on-error=false #=========本地=========== spring.datasource.url=jdbc:mysql://localhost:3306/swapping?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
#=======虛擬機========== #spring.datasource.url=jdbc:mysql://192.168.92.130:3306/swapping?useSSL=false&useUnicode=true&characterEncoding=UTF-8
spring.datasource.username=root spring.datasource.password=mynewpassword123 spring.datasource.driver-class-name=com.mysql.jdbc.Driver #druid 下面為連接池的補充設置,應用到上面所有數據源中 spring.datasource.type=com.alibaba.druid.pool.DruidDataSource # 初始化大小,最小,最大 spring.datasource.initialSize=5 spring.datasource.minIdle=5 spring.datasource.maxActive=20 # 配置獲取連接等待超時的時間 spring.datasource.maxWait=60000 # 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒 spring.datasource.timeBetweenEvictionRunsMillis=60000 # 配置一個連接在池中最小生存的時間,單位是毫秒 spring.datasource.minEvictableIdleTimeMillis=300000 spring.datasource.validationQuery=SELECT 'x' spring.datasource.testWhileIdle=true spring.datasource.testOnBorrow=false spring.datasource.testOnReturn=false # 打開PSCache,並且指定每個連接上PSCache的大小 spring.datasource.poolPreparedStatements=true spring.datasource.maxPoolPreparedStatementPerConnectionSize=20 spring.datasource.maxOpenPreparedStatements=20 # 配置監控統計攔截的filters,去掉后監控界面sql無法統計,'wall'用於防火牆 spring.datasource.filters=stat,wall,log4j # 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄 spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 # 合並多個DruidDataSource的監控數據 #spring.datasource.useGlobalDataSourceStat=true #mybatis相關配置 參考地址:https://mybatis.org/mybatis-3/zh/index.html
#mybatis映射文件的位置 mybatis.mapper-locations=classpath:mapper/*.xml #mybatis指定entity位置 mybatis.type-aliases-package=com.sxd.swapping.mybatis.pojo #mybatis展示sql語句執行 logging.level.com.sxd.swapping=debug #允許 JDBC 支持自動生成主鍵,需要數據庫驅動支持。如果設置為 true,將強制使用自動生成主鍵。盡管一些數據庫驅動不支持此特性,但仍可正常工作 mybatis.configuration.use-generated-keys=true #是否開啟駝峰命名自動映射,即從經典數據庫列名 A_COLUMN 映射到經典 Java 屬性名 aColumn mybatis.configuration.map-underscore-to-camel-case=true #pagehelper mybatis分頁插件 pagehelper.helperDialect=mysql pagehelper.reasonable=true pagehelper.supportMethodsArguments=true pagehelper.params=count=countSql pagehelper.returnPageInfo=check #jpa相關配置 spring.jpa.database=mysql spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=update spring.jpa.database-platform=org.hibernate.dialect.MySQL55Dialect #redis配置 # Redis數據庫索引(默認為0) spring.redis.database=0 # Redis服務器地址 #======本地======= spring.redis.host=localhost #=======虛擬機======= #spring.redis.host=192.168.92.130 # Redis服務器連接端口 spring.redis.port=6379 # Redis服務器連接密碼(默認為空) spring.redis.password=398023 # 連接池最大連接數(使用負值表示沒有限制) spring.redis.jedis.pool.max-active=8 # 連接池最大阻塞等待時間(使用負值表示沒有限制) spring.redis.jedi.pool.max-wait=-1 # 連接池中的最大空閑連接 spring.redis.jedi.pool.max-idle=8 # 連接池中的最小空閑連接 spring.redis.jedi.pool.min-idle=0 # 連接超時時間(毫秒) spring.redis.jedi.timeout=0 #elasticsearch相關配置 #es的cluster集群名稱可以查看服務器安裝的集群名稱 curl http://192.168.92.130:9200 獲取到集群名稱 spring.data.elasticsearch.cluster-name=docker-cluster #注意端口為9300 9300 是 Java 客戶端的端口,支持集群之間的通信。9200 是支持 Restful HTTP 的接口 spring.data.elasticsearch.cluster-nodes=192.168.92.130:9300 #logback對接logstash的日志配置文件 logging.config=classpath:logback-spring.xml #線程池配置 thread.pool.core.size=10 thread.pool.max.size=10 thread.pool.queue.capacity=10000 thread.pool.alive.seconds=1000
請求實時下載:
http://localhost:9666/xlsx/downloadAndUpload/rtDownload
生成報表結果展示: