導讀
下午抽空封裝一個通用導出Excel工具類。之前還寫過一篇EasyPoi導入參數校驗,批注導出,點我直達
添加依賴
<!-- easy poi --> <dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-spring-boot-starter</artifactId> <version>4.1.0</version> </dependency>
注意依賴版本超過4.1.0導出圖片時有問題喲
EasyPoi工具類
時間工具類
import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.Date; /** * 時間工具類 * * @Author:chenyanbin */ public class TimeUtil { public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"; public static final String YYYY_MM_DD = "yyyy-MM-dd"; public static final String YYMMDD = "yyMMdd"; public static final String YYYYMMDD = "yyyyMMdd"; /** * 格式化日期 * * @param time 當前時間 * @return */ public static String format(Date time) { return format(time, YYYY_MM_DD_HH_MM_SS); } /** * 格式化日期 * * @param time 當前時間 * @param pattern 格式化規則 * @return */ public static String format(Date time, String pattern) { DateTimeFormatter dtf = DateTimeFormatter.ofPattern(pattern); ZoneId zoneId = ZoneId.systemDefault(); return dtf.format(time.toInstant().atZone(zoneId)); } /** * timestamp 轉 字符串 * * @param timestamp * @return */ public static String dateToStr(long timestamp) { return dateToStr(timestamp, YYYY_MM_DD_HH_MM_SS); } /** * timestamp 轉 字符串 * * @param timestamp 時間戳 * @param pattern 格式化規則 * @return */ public static String dateToStr(long timestamp, String pattern) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); ZoneId zoneId = ZoneId.systemDefault(); String timeStr = formatter.format(new Date(timestamp).toInstant().atZone(zoneId)); return timeStr; } /** * 字符串 轉 date * * @param time * @return */ public static Date strToDate(String time) { return strToDate(time, YYYY_MM_DD_HH_MM_SS); } /** * 字符串 轉 date * * @param time 當前時間 * @param pattern 格式化規則 * @return */ public static Date strToDate(String time, String pattern) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); LocalDateTime localDateTime = LocalDateTime.parse(time, formatter); return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); } /** * 當前時間格式化 * * @return */ public static String currentTimeFormat() { LocalDateTime nowDate = LocalDateTime.now(); DateTimeFormatter dtf = DateTimeFormatter.ofPattern(YYYY_MM_DD_HH_MM_SS); return dtf.format(nowDate); } /** * 當前時間格式化 * * @param pattern 格式化規則 * @return */ public static String currentTimeFormat(String pattern) { LocalDateTime nowDate = LocalDateTime.now(); DateTimeFormatter dtf = DateTimeFormatter.ofPattern(pattern); return dtf.format(nowDate); } }
導出樣式
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity; import cn.afterturn.easypoi.excel.entity.params.ExcelForEachParams; import cn.afterturn.easypoi.excel.export.styler.IExcelExportStyler; import org.apache.poi.ss.usermodel.*; /** * Easy Poi 導出樣式 * @Author:chenyanbin */ public class EasyPoiExcelExportStylerUitl implements IExcelExportStyler { private static final short STRING_FORMAT = (short) BuiltinFormats.getBuiltinFormat("TEXT"); private static final short FONT_SIZE_TEN = 10; private static final short FONT_SIZE_ELEVEN = 11; private static final short FONT_SIZE_TWELVE = 12; /** * 大標題樣式 */ private CellStyle headerStyle; /** * 每列標題樣式 */ private CellStyle titleStyle; /** * 數據行樣式 */ private CellStyle styles; public EasyPoiExcelExportStylerUitl(Workbook workbook) { this.init(workbook); } /** * 初始化樣式 * * @param workbook */ private void init(Workbook workbook) { this.headerStyle = initHeaderStyle(workbook); this.titleStyle = initTitleStyle(workbook); this.styles = initStyles(workbook); } /** * 大標題樣式 * * @param color * @return */ @Override public CellStyle getHeaderStyle(short color) { return headerStyle; } /** * 每列標題樣式 * * @param color * @return */ @Override public CellStyle getTitleStyle(short color) { return titleStyle; } /** * 數據行樣式 * * @param parity 可以用來表示奇偶行 * @param entity 數據內容 * @return 樣式 */ @Override public CellStyle getStyles(boolean parity, ExcelExportEntity entity) { return styles; } /** * 獲取樣式方法 * * @param dataRow 數據行 * @param obj 對象 * @param data 數據 */ @Override public CellStyle getStyles(Cell cell, int dataRow, ExcelExportEntity entity, Object obj, Object data) { return getStyles(true, entity); } /** * 模板使用的樣式設置 */ @Override public CellStyle getTemplateStyles(boolean isSingle, ExcelForEachParams excelForEachParams) { return null; } /** * 初始化--大標題樣式 * * @param workbook * @return */ private CellStyle initHeaderStyle(Workbook workbook) { CellStyle style = getBaseCellStyle(workbook); style.setFont(getFont(workbook, (short) 14, true)); return style; } /** * 初始化--每列標題樣式 * * @param workbook * @return */ private CellStyle initTitleStyle(Workbook workbook) { CellStyle style = getBaseCellStyle(workbook); style.setFont(getFont(workbook, FONT_SIZE_TWELVE, true)); //背景色 // style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); // style.setFillPattern(FillPatternType.SOLID_FOREGROUND); style.setDataFormat(STRING_FORMAT); return style; } /** * 初始化--數據行樣式 * * @param workbook * @return */ private CellStyle initStyles(Workbook workbook) { CellStyle style = getBaseCellStyle(workbook); style.setFont(getFont(workbook, FONT_SIZE_ELEVEN, false)); //背景色 // style.setFillForegroundColor(IndexedColors.RED.getIndex()); // style.setFillPattern(FillPatternType.SOLID_FOREGROUND); style.setDataFormat(STRING_FORMAT); return style; } /** * 基礎樣式 * * @return */ private CellStyle getBaseCellStyle(Workbook workbook) { CellStyle style = workbook.createCellStyle(); //下邊框 style.setBorderBottom(BorderStyle.THIN); //左邊框 style.setBorderLeft(BorderStyle.THIN); //上邊框 style.setBorderTop(BorderStyle.THIN); //右邊框 style.setBorderRight(BorderStyle.THIN); //水平居中 style.setAlignment(HorizontalAlignment.CENTER); //上下居中 style.setVerticalAlignment(VerticalAlignment.CENTER); //設置自動換行 style.setWrapText(true); return style; } /** * 字體樣式 * * @param size 字體大小 * @param isBold 是否加粗 * @return */ private Font getFont(Workbook workbook, short size, boolean isBold) { Font font = workbook.createFont(); //字體樣式 font.setFontName("宋體"); //是否加粗 font.setBold(isBold); //字體大小 font.setFontHeightInPoints(size); return font; } }
導出工具類
import cn.afterturn.easypoi.excel.ExcelExportUtil; import cn.afterturn.easypoi.excel.entity.ExportParams; import cn.afterturn.easypoi.excel.entity.enmus.ExcelType; import lombok.extern.slf4j.Slf4j; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URLEncoder; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; /** * EasyPoi 導出工具類 * * @Author:chenyanbin */ @Slf4j public class EasyPoiExportUtil { private static final Long KEEP_ALIVE_TIME = 60L; private static final int APS = Runtime.getRuntime().availableProcessors(); private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(APS * 2, APS * 4, KEEP_ALIVE_TIME, TimeUnit.SECONDS, new LinkedBlockingDeque<>()); /** * 導出Excel 一對多關系 * * @param list Excel數據集合 * @param title Excel第一行標題,如果設置為null,默認不顯示 * @param sheetName sheet名稱 * @param pojoClass 泛型List的對象 * @param fileName 導出文件名 * @param type excel類型 HSSF || XSSF * @param isOneToMany 是否一對多 * @param response 響應體 */ public static void exportOneToManyExcel( List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName, ExcelType type, boolean isOneToMany, HttpServletResponse response ) { ExportParams exportParams = new ExportParams(title, sheetName, type); exportParams.setStyle(EasyPoiExcelExportStylerUitl.class); AtomicReference<Workbook> workbook = new AtomicReference<>(); workbookHandler(workbook, exportParams, pojoClass, list); if (workbook.get() == null) { return; } //判斷是否是一對多 if (isOneToMany) { setRowHeight(workbook.get()); } downLoadExcel(fileName, response, workbook.get()); } /** * 導出excel * * @param list Excel數據集合 * @param title Excel第一行標題,如果設置為null,默認不顯示 * @param sheetName sheet名稱 * @param pojoClass 泛型List的對象 * @param fileName 導出文件名 * @param setRowHeight 是否行高自適應 * @param response 響應體 */ public static void exportOneExcel( List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName, boolean setRowHeight, HttpServletResponse response ) { ExportParams exportParams = new ExportParams(title, sheetName); exportParams.setStyle(EasyPoiExcelExportStylerUitl.class); AtomicReference<Workbook> workbook = new AtomicReference<>(); workbookHandler(workbook, exportParams, pojoClass, list); if (workbook.get() == null) { return; } //判斷是否根據內容自適應行高 if (setRowHeight) { Sheet sheet = workbook.get().getSheetAt(0); for (int i = 0; i <= sheet.getLastRowNum(); i++) { Row row = sheet.getRow(i); setRowHeight(row); } } downLoadExcel(fileName, response, workbook.get()); } /** * 下載Excel * * @param fileName * @param response * @param workbook */ private static void downLoadExcel(String fileName, HttpServletResponse response, Workbook workbook) { ServletOutputStream outputStream = null; try { outputStream = response.getOutputStream(); response.setCharacterEncoding("UTF-8"); response.setHeader("content-Type", "application/vnd.ms-excel"); response.setHeader("Pragma", "No-cache"); response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8")); workbook.write(outputStream); outputStream.flush(); } catch (IOException e) { throw new RuntimeException(e.getMessage()); } finally { if (outputStream != null) { try { outputStream.close(); } catch (IOException e) { log.error("excel導出關閉輸出流異常:{}", e.getMessage()); } } } } /** * 一對多,設置行高 */ private static void setRowHeight(Workbook workbook) { Sheet sheet = workbook.getSheetAt(0); //設置第4列的列寬為60(下標從0開始),TestExportSub2Vo 不知道為什么設置了列寬但是不起作用,只能在這里單獨設置 sheet.setColumnWidth(3, 60 * 256); for (int i = 0; i <= sheet.getLastRowNum(); i++) { Row row = sheet.getRow(i); if (i == 0) { //設置第一行的行高(表格標題) row.setHeightInPoints(35); } else if (i == 1) { //設置第二行的行高(表格表頭) row.setHeightInPoints(25); } else { //設置其他行的行高根據內容自適應 setRowHeight(row); } } } private static void setRowHeight(Row row) { //根據內容長度設置行高 int enterCnt = 0; for (int j = 0; j < row.getPhysicalNumberOfCells(); j++) { int rwsTemp = row.getCell(j).toString().length(); //這里取每一行中的每一列字符長度最大的那一列的字符 if (rwsTemp > enterCnt) { enterCnt = rwsTemp; } } //設置默認行高為35 row.setHeightInPoints(35); //如果字符長度大於35,判斷大了多少倍,根據倍數來設置相應的行高 if (enterCnt > 35) { float d = enterCnt / 35; float f = 35 * d; /*if (d>2 && d<4){ f = 35*2; }else if(d>=4 && d<6){ f = 35*3; }else if (d>=6 && d<8){ f = 35*4; }*/ row.setHeightInPoints(f); } } private static void workbookHandler(AtomicReference<Workbook> workbook, ExportParams exportParams, Class<?> pojoClass, List<?> list) { CountDownLatch latch = new CountDownLatch(1); THREAD_POOL_EXECUTOR.execute(() -> { try { workbook.set(ExcelExportUtil.exportExcel(exportParams, pojoClass, list)); } finally { latch.countDown(); } }); try { latch.await(); } catch (InterruptedException e) { log.error("多線程導出等待異常:{}", e.getMessage()); } } }
根據url下載圖片轉字節數組
import lombok.extern.slf4j.Slf4j; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; /** * Http工具類 * * @Author:chenyanbin */ @Slf4j public class HttpUtil { /** * 獲取網絡圖片轉成字節流 * * @param strUrl 完整圖片地址 * @return 圖片資源數組 */ public static byte[] getNetImgByUrl(String strUrl) { try { URL url = new URL(strUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(2 * 1000); // 通過輸入流獲取圖片數據 InputStream inStream = conn.getInputStream(); // 得到圖片的二進制數據 byte[] btImg = readInputStream(inStream); return btImg; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 從輸入流中獲取字節流數據 * * @param inStream 輸入流 * @return 圖片流 */ private static byte[] readInputStream(InputStream inStream) throws Exception { ByteArrayOutputStream outStream = new ByteArrayOutputStream(); // 設置每次讀取緩存區大小 byte[] buffer = new byte[1024 * 10]; int len = 0; while ((len = inStream.read(buffer)) != -1) { outStream.write(buffer, 0, len); } inStream.close(); return outStream.toByteArray(); } }
導出實體類vo
import cn.afterturn.easypoi.excel.annotation.Excel; import io.swagger.annotations.ApiModel; import lombok.Data; /** * @Author:chenyanbin */ @Data @ApiModel(value = "ProductFilingExportExcelVo對象", description = "商品備案明細導出信息") public class ProductFilingExportExcelVo { /** * 主鍵 */ private Long id; @Excel(name = "商品編碼", width = 20) private String productCode; @Excel(name = "HS編碼", width = 25) private String hsCode; @Excel(name = "商品名稱", width = 40) private String productName; @Excel(name = "品牌") private String brandName; @Excel(name = "規格型號", width = 20) private String specificationModel; @Excel(name = "申報要素", width = 40) private String reportElements; @Excel(name = "產品成分", width = 50) private String productComposition; @Excel(name = "生產企業", width = 25) private String enterprise; @Excel(name = "生產國/地區", width = 20) private String country; @Excel(name = "適用標准", width = 20) private String standards; @Excel(name = "商品條碼", width = 30) private String productBarCode; @Excel(name = "功能", width = 40) private String function; @Excel(name = "其他說明", width = 40) private String remark; @Excel(name = "商品正面", type = 2, imageType = 2) private byte[] productFrontPic; /** * 臨時商品正面url */ private String tempProductFrontUrl; @Excel(name = "商品背面", type = 2, imageType = 2) private byte[] productBackPic; /** * 臨時商品背面url */ private String tempProductBackUrl; @Excel(name = "商品其他面", width = 15, type = 2, imageType = 2) private byte[] productOtherPic; /** * 臨時商品其他url */ private String tempProductOtherUrl; }
dao查詢sql語句
<resultMap id="exportExcelMap" type="ProductFilingExportExcelVo"> <id column="id" property="id"/> <result column="product_code" property="productCode"/> <result column="hs_code" property="hsCode"/> <result column="product_name" property="productName"/> <result column="brand_name" property="brandName"/> <result column="specification_model" property="specificationModel"/> <result column="report_elements" property="reportElements"/> <result column="product_composition" property="productComposition"/> <result column="enterprise" property="enterprise"/> <result column="country" property="country"/> <result column="standards" property="standards"/> <result column="product_bar_code" property="productBarCode"/> <result column="function" property="function"/> <result column="remark" property="remark"/> <result column="temp_product_front_url" property="tempProductFrontUrl"/> <result column="temp_product_back_url" property="tempProductBackUrl"/> <result column="temp_product_other_url" property="tempProductOtherUrl"/> </resultMap> <!-- excel導出 --> <select id="exportExcel" resultMap="exportExcelMap"> SELECT bpfd.id, bpfd.product_code, bpfd.hs_code, bpfd.product_name, bb.brand_name, bpfd.specification_model, bpfd.report_elements, bpfd.product_composition, bpfd.enterprise, bpfd.country, bpfd.standards, bpfd.product_bar_code, bpfd.`function`, bpfd.remark, bof.url_path temp_product_front_url, bob.url_path temp_product_back_url, boo.url_path temp_product_other_url FROM basic_product_filing_detail bpfd LEFT JOIN basic_brand bb ON bb.id = bpfd.brand_id LEFT JOIN basic_oss bof ON ( bof.id = bpfd.product_front_id AND bof.del_flag = 0 ) LEFT JOIN basic_oss bob ON ( bob.id = bpfd.product_back_id AND bob.del_flag = 0 ) LEFT JOIN basic_oss boo ON ( boo.id = bpfd.product_other_id AND boo.del_flag = 0 ) WHERE bpfd.product_filing_id = #{filing_id} ORDER BY bpfd.id DESC </select>
controller層
@ApiOperation("導出") @GetMapping("export_detail_excel") public void exportDetailExcel( HttpServletResponse response, @ApiParam(value = "主表主鍵id", required = true) @RequestParam(value = "id") Long id ) { cacheService.exportDetailExcel(response, id); }
service層
import javax.servlet.http.HttpServletResponse; public interface ProductFilingCacheService { /** * 導出 * @param response 響應體 * @param id 主表主鍵id */ void exportDetailExcel(HttpServletResponse response, Long id); }
service實現類!!!! private static final Long KEEP_ALIVE_TIME = 60L; private static final int APS = Runtime.getRuntime().availableProcessors(); private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(APS * 2, APS * 4, KEEP_ALIVE_TIME, TimeUnit.SECONDS, new LinkedBlockingDeque<>()); @Override public void exportDetailExcel(HttpServletResponse response, Long id) { List<ProductFilingExportExcelVo> excelList = filingDetailMapper.exportExcel(id); CountDownLatch latch = new CountDownLatch(excelList.size()); //轉換圖片字節碼 excelList.forEach(obj -> { try { if (StringUtils.isNotBlank(obj.getTempProductFrontUrl())) { obj.setProductFrontPic(HttpUtil.getNetImgByUrl(obj.getTempProductFrontUrl())); } if (StringUtils.isNotBlank(obj.getTempProductBackUrl())) { obj.setProductBackPic(HttpUtil.getNetImgByUrl(obj.getTempProductBackUrl())); } if (StringUtils.isNotBlank(obj.getTempProductOtherUrl())) { obj.setProductOtherPic(HttpUtil.getNetImgByUrl(obj.getTempProductOtherUrl())); } } finally { latch.countDown(); } }); try { latch.await(); } catch (InterruptedException e) { log.error("導出Excel等待下載文件流異常:{}", e.getMessage()); } //獲取貨主名稱 String cargoOwnerName = ""; ProductFilingDO productFilingDO = filingMapper.selectById(id); if (productFilingDO != null) { Long createUserId = productFilingDO.getCreateUserId(); JsonData<String> jsonData = userControllerFeign.getCargoOwnerNameById(createUserId); if (JsonData.isSuccess(jsonData)) { cargoOwnerName = jsonData.getData(); } } String fileName = cargoOwnerName + "-" + TimeUtil.currentTimeFormat(TimeUtil.YYMMDD) + ".xls"; //導出 EasyPoiExportUtil.exportOneExcel( excelList, fileName.replace(".xls", ""), "商品備案明細", ProductFilingExportExcelVo.class, fileName, false, response ); }
如果需要設置導出行高自適應,使用如下導出!!!!! false改成true //導出 EasyPoiExportUtil.exportOneExcel( excelList, fileName.replace(".xls", ""), "商品備案明細", ProductFilingExportExcelVo.class, fileName, true, response );
演示