UtilExcel.java 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. package com.malk.utils;
  2. import cn.hutool.core.util.ObjectUtil;
  3. import com.alibaba.excel.EasyExcel;
  4. import com.alibaba.excel.ExcelWriter;
  5. import com.alibaba.excel.write.metadata.WriteSheet;
  6. import com.google.common.base.Strings;
  7. import com.malk.server.common.McException;
  8. import com.malk.server.common.McREnum;
  9. import lombok.Builder;
  10. import lombok.Data;
  11. import lombok.SneakyThrows;
  12. import org.apache.commons.lang3.StringUtils;
  13. import org.apache.poi.ss.usermodel.BorderStyle;
  14. import org.apache.poi.xssf.usermodel.*;
  15. import javax.annotation.Nullable;
  16. import javax.servlet.http.HttpServletResponse;
  17. import javax.validation.constraints.NotNull;
  18. import java.io.IOException;
  19. import java.io.InputStream;
  20. import java.io.OutputStream;
  21. import java.math.BigDecimal;
  22. import java.net.URLEncoder;
  23. import java.text.SimpleDateFormat;
  24. import java.util.Date;
  25. import java.util.List;
  26. import java.util.Map;
  27. /**
  28. * excel 导出工具 [poi & easy excel]
  29. */
  30. @Builder
  31. @Data
  32. public class UtilExcel {
  33. /**
  34. * 设置响应流
  35. */
  36. @SneakyThrows
  37. public static void setResponseHeader(HttpServletResponse response, @NotNull String fileName, String extension) {
  38. // 后缀: poi, xlsx下预览无详情; EasyExcel 需要使用xlsx, 否则不能预览
  39. if (StringUtils.isBlank(extension)) {
  40. extension = ".xls";
  41. }
  42. String date = new SimpleDateFormat("yyyy-MM-dd HH_mm_ss").format(new Date());
  43. fileName = fileName + "_" + date + extension;
  44. // 设置导出流信息
  45. response.setContentType("application/vnd.ms-excel;charset=UTF-8");
  46. response.setCharacterEncoding("utf-8");
  47. // 文件名兼容中文
  48. fileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
  49. response.setHeader("Content-Disposition", "attachment;filename*=utf-8" + fileName);
  50. // 想要让客户端CellStyleModel可以访问到其他的首部信息,服务器不仅要在header里加入该首部,还要将它们在 Access-Control-Expose-Headers 里面列出来
  51. response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
  52. }
  53. /////////////////// Poi ///////////////////
  54. // 各个列的表头
  55. private String[] heardList;
  56. // 各个列的元素key值
  57. private String[] heardKey;
  58. // 需要填充的数据信息
  59. private List<Map> data;
  60. // 导出文件名称
  61. private String fileName;
  62. // 字体大小
  63. @Builder.Default
  64. private int fontSize = 12;
  65. @Builder.Default
  66. private String fontName = "微软雅黑";
  67. // 行高
  68. @Builder.Default
  69. private int rowHeight = 30;
  70. // 列宽
  71. @Builder.Default
  72. private int columnWidth = 20;
  73. // 工作表
  74. @Builder.Default
  75. private String sheetName = "sheet1";
  76. /**
  77. * 回调单元格样式
  78. * -
  79. * cellStyle.setFillForegroundColor(IndexedColors.RED.getIndex()); // 是设置前景色不是背景色
  80. * cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
  81. */
  82. @FunctionalInterface
  83. public interface UpdateCellStyle {
  84. void invoke(String value, XSSFCellStyle cellStyle);
  85. }
  86. public void exportExcelByPoi(HttpServletResponse response) {
  87. exportExcelByPoi(response, null);
  88. }
  89. /**
  90. * Poi 导出功能
  91. * -
  92. * - java中的强制类型转换只是针对单个对象的,想要偷懒将整个数组转换成另外一种类型的数组是不行的. toArray 运行中已经转为 Object, 需要指定初始化类型
  93. * - 4.x打包引用会异常: HSSFCellStyle.BORDER_THIN 指向的是 CellStyle.BORDER_THIN. 另若引入 easyExcel, 无需再引入 poi [其依赖是低版本 poi]
  94. * - 数量限制: HSSF改为XSSF后,导出1000000条数据. HSSF最大容量为65535 [最大65536(2的16次方)行;即横向256个单元格,竖向65536个单元格]
  95. */
  96. public void exportExcelByPoi(HttpServletResponse response, UpdateCellStyle lambda) {
  97. // 检查参数配置信息
  98. checkConfig();
  99. // 创建工作簿
  100. XSSFWorkbook wb = new XSSFWorkbook();
  101. // 创建工作表
  102. XSSFSheet wbSheet = wb.createSheet(sheetName);
  103. // 设置默认行宽
  104. wbSheet.setDefaultColumnWidth(columnWidth);
  105. // 设置表格样式
  106. XSSFCellStyle styleHeader = createCellStyle(wb);
  107. // 设置表头字体
  108. XSSFFont fontHeader = wb.createFont();
  109. fontHeader.setFontHeightInPoints((short) fontSize);
  110. fontHeader.setFontName(fontName);
  111. fontHeader.setBold(true);
  112. styleHeader.setFont(fontHeader);
  113. //设置列头元素
  114. XSSFRow row = wbSheet.createRow(0);
  115. XSSFCell cellHead = null;
  116. for (int i = 0; i < heardList.length; i++) {
  117. cellHead = row.createCell(i);
  118. cellHead.setCellValue(heardList[i]);
  119. cellHead.setCellStyle(styleHeader);
  120. }
  121. // 设置表格样式
  122. XSSFCellStyle bodyStyle = createCellStyle(wb);
  123. // 设置表格字体
  124. XSSFFont fontBody = wb.createFont();
  125. fontBody.setFontHeightInPoints((short) fontSize);
  126. fontBody.setFontName(fontName);
  127. fontBody.setBold(false);
  128. bodyStyle.setFont(fontBody);
  129. //开始写入实体数据信息
  130. int a = 1;
  131. for (int i = 0; i < data.size(); i++) {
  132. XSSFRow roww = wbSheet.createRow(a);
  133. Map map = data.get(i);
  134. XSSFCell cell = null;
  135. for (int j = 0; j < heardKey.length; j++) {
  136. cell = roww.createCell(j);
  137. cell.setCellStyle(bodyStyle);
  138. Object valueObject = map.get(heardKey[j]);
  139. String value = null;
  140. if (valueObject == null) {
  141. valueObject = "";
  142. }
  143. if (valueObject instanceof String) {
  144. //取出的数据是字符串直接赋值
  145. value = (String) map.get(heardKey[j]);
  146. } else if (valueObject instanceof Integer) {
  147. //取出的数据是Integer
  148. value = String.valueOf(((Integer) (valueObject)).floatValue());
  149. } else if (valueObject instanceof BigDecimal) {
  150. //取出的数据是BigDecimal
  151. value = String.valueOf(((BigDecimal) (valueObject)).floatValue());
  152. } else {
  153. value = valueObject.toString();
  154. }
  155. cell.setCellValue(Strings.isNullOrEmpty(value) ? "" : value);
  156. // 独立设置单元格样式
  157. if (ObjectUtil.isNotNull(lambda)) {
  158. XSSFCellStyle newBodyStyle = createCellStyle(wb);
  159. newBodyStyle.setFont(fontBody);
  160. lambda.invoke(value, newBodyStyle);
  161. cell.setCellStyle(newBodyStyle);
  162. }
  163. }
  164. a++;
  165. }
  166. // 导出文件
  167. setResponseHeader(response, fileName, ".xls");
  168. try {
  169. // 取得输出流
  170. OutputStream os = response.getOutputStream();
  171. wb.write(os);
  172. os.flush();
  173. os.close();
  174. } catch (IOException ex) {
  175. throw new McException(McREnum.METHOD_EXECUTE);
  176. }
  177. }
  178. // 不同的 CellStyle 需要独立创建
  179. private XSSFCellStyle createCellStyle(XSSFWorkbook wb) {
  180. XSSFCellStyle style = wb.createCellStyle();
  181. style.setBorderBottom(BorderStyle.THIN); //下边框
  182. style.setBorderLeft(BorderStyle.THIN); //左边框
  183. style.setBorderTop(BorderStyle.THIN); //上边框
  184. style.setBorderRight(BorderStyle.THIN); //右边框
  185. return style;
  186. }
  187. // 检查数据配置问题
  188. protected void checkConfig() {
  189. if (heardKey == null || heardList.length == 0) {
  190. McException.exceptionParam("列名数组不能为空或者为NULL");
  191. }
  192. if (fontSize < 0 || rowHeight < 0 || columnWidth < 0) {
  193. McException.exceptionParam("字体、宽度或者高度不能为负值");
  194. }
  195. if (Strings.isNullOrEmpty(fileName)) {
  196. McException.exceptionParam("导出文件名称不能为NULL");
  197. }
  198. }
  199. /////////////////// EasyExcel ///////////////////
  200. /**
  201. * 导出功能 EasyExcel [class支持map]
  202. * -
  203. * - Mac下, 若报错 Times 字体找不到, 下载安装一下, 不影响功能使用
  204. * - ClassPathResource, 需要打包/编译后才能访问到. 识别不是架包内内容
  205. */
  206. @SneakyThrows
  207. public static void exportListByTemplate(HttpServletResponse response, List dataList, Class dtoClass, @Nullable String fileName, String templateName) {
  208. InputStream inputStream = UtilFile.readPackageResource("templates/" + templateName);
  209. UtilExcel.setResponseHeader(response, fileName, ".xlsx");
  210. // 模板导出
  211. EasyExcel.write(response.getOutputStream(), dtoClass).withTemplate(inputStream).sheet().doFill(dataList);
  212. }
  213. /**
  214. * 列表与主表进行填充 [格式: 模板主表 {字段}, 列表 {.字段}]
  215. */
  216. @SneakyThrows
  217. public static void exportMapAndListByTemplate(HttpServletResponse response, Object dataMain, List dataList, Class dtoClass, @Nullable String fileName, String templateName) {
  218. InputStream inputStream = UtilFile.readPackageResource("templates/" + templateName);
  219. UtilExcel.setResponseHeader(response, fileName, ".xlsx");
  220. ExcelWriter workBook = EasyExcel.write(response.getOutputStream(), dtoClass).withTemplate(inputStream).build();
  221. WriteSheet sheet = EasyExcel.writerSheet("HL2025001").build();
  222. // 先单组数据填充,再多组数据填充
  223. workBook.fill(dataMain, sheet);
  224. workBook.fill(dataList, sheet);
  225. workBook.finish();
  226. }
  227. /**
  228. * 列表与主表进行填充 [格式: 模板主表 {字段}, 列表 {.字段}]
  229. */
  230. @SneakyThrows
  231. public static void exportMapAndListByTemplate(HttpServletResponse response, List dataList, Class dtoClass, @Nullable String fileName, String templateName) {
  232. InputStream inputStream = UtilFile.readPackageResource("templates/" + templateName);
  233. UtilExcel.setResponseHeader(response, fileName, ".xlsx");
  234. ExcelWriter workBook = EasyExcel.write(response.getOutputStream(), dtoClass).withTemplate(inputStream).build();
  235. WriteSheet sheet = EasyExcel.writerSheet().build();
  236. // 先单组数据填充,再多组数据填充
  237. workBook.fill(dataList, sheet);
  238. workBook.finish();
  239. }
  240. }