前言

最近有关于excel下载的需求,写此文章记录技术要点。

功能需求:

  1. 要求根据页面选择数据下载成excel;
  2. 根据选择类型分组,下载成多个excel并打包成zip压缩文件并通过浏览器下载

一、项目简介

后端项目使用springboot 服务框架。前端使用vue框架

二、使用步骤

1.Maven包依赖

主要使用hutool,easyExcellombok

pom.xml文件中dependencies标签加入依赖:

<!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.2.6</version>
</dependency>
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>4.1.0</version>
</dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.8</version>
</dependency>

2.建立EasayExcel数据模板实体类

在实体类属性上使用标签,具体查看easyExcel官网:

@ExcelProperty:指定当前字段对应excel中的那一列,value表头名称,index指定列位置,从0开始;

@ExcelIgnore:默认所有字段都会和excel去匹配,加了这个注解会忽略该字段;

@DateTimeFormat:  日期转换,用String去接收excel日期格式的数据会调用这个

@JsonFormat:日期转换,实体类转json时会用到

代码如下:

package com.xxx.project.vo;import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;/*** @Author zhaochun* @Date 2022年06月21日 10:11* @Description 课表列表**/
@ApiModel("课表列表")
@Data
public class TimeTableVo {@ExcelProperty(value = "日期",index = 1)@ApiModelProperty("排课日期")@DateTimeFormat("yyyy/MM/dd")@JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8")private Date  classDate;@ExcelIgnore@ApiModelProperty("排课上课时间")@JsonFormat(pattern = "HH:mm",timezone = "GMT+8")private Date  classBeginTime;@ExcelIgnore@ApiModelProperty("排课下课时间")@JsonFormat(pattern = "HH:mm",timezone = "GMT+8")private Date  classEndTime;@ExcelProperty(value = "时间",index = 2)@JsonIgnore@ApiModelProperty(value = "上下课时间",hidden = true)private String classTime;@ExcelProperty(value = "班级名称",index = 0)@ApiModelProperty("班级名称")private String className;@ExcelProperty(value = "课程",index = 3)@ApiModelProperty("课程名称")private String lessonName;@ExcelProperty(value = "课时",index = 4)@ApiModelProperty("课时数")private BigDecimal classHour;@ExcelProperty(value = "教师",index = 5)@ApiModelProperty("老师名称")private String teacherName;@ExcelProperty(value = "教室",index = 6)@ApiModelProperty("培训教室+地点")private String roomLocation;@ExcelIgnore@ApiModelProperty("培训教室")private String roomName;@ExcelIgnore@ApiModelProperty("学员数量")private Integer studentNum;@ExcelIgnore@ApiModelProperty("课节类型 1课程  2考试")private Integer lessonsType;}

2.自定义拦截器(CustomCellWriteUtil)

拦截器有很多种,具体查看easyExcel官网。

这里使用了easyExcel定义的拦截器AbstractCellStyleStrategy,继承它就好了。

AbstractCellStyleStrategy拦截器里已经实现afterCellDispose (字面意思是在单元格被设置之后)方法由于需要设置单元格自适应长度,所以重写这个方法,并调用父级方法。

样式的设置是参照:xudongbase: 主要是项目中可以用到的共通方法,现有easyexcel分支在持续更新中。欢迎大家Star和提交Issues。easyexcel分支:批量设置样式,批量添加批注,批量合并单元格,设置冻结行和列,设置行高列宽,隐藏行和列,绑定下拉框数据,设置水印,插入图片https://gitee.com/xudong_master/xudongbase.git

单元格自适应长度参照官网:LongestMatchColumnWidthData,但是这个拦截器对中文和非中文的混合字符串不是很友好,在百度找到可以统计中文字符的方式计算列长度(这篇博文现在找不到了)。但是这个仍然有个问题:没法适应字体大小,字体太大仍然会出现列宽不够的情况。

poi列宽单位:为一个字符宽度的1/256

poi行高单位:twips(缇) ;Excel行高 单位pt(磅); 1pt = 20twips

package com.xxx.project.config;import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.util.CollectionUtils;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import com.alibaba.excel.write.style.AbstractCellStyleStrategy;
import com.bigunion.project.utils.RegUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.*;
import java.awt.Color;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** @Author zhaochun* @Date 2022年06月21日 18:10* @Description excel自适应列宽**/
public class CustomCellWriteUtil extends AbstractCellStyleStrategy {private static final int MAX_COLUMN_WIDTH = 255 * 2 * 256 ;private Map<Integer, Map<Integer, Integer>> cache = new HashMap(8);//前景色 gbkprivate final XSSFColor bgColor = new XSSFColor(new Color(215, 226, 244), new DefaultIndexedColorMap());
//字体颜色private final XSSFColor fontColor = new XSSFColor(new Color(71, 143, 247), new DefaultIndexedColorMap());private XSSFCellStyle headerStyle;private XSSFCellStyle contendStyle;@Overridepublic void afterWorkbookCreate(WriteWorkbookHolder writeWorkbookHolder) {super.afterWorkbookCreate(writeWorkbookHolder);}@Overrideprotected void initCellStyle(Workbook workbook) {if (headerStyle == null) {headerStyle = (XSSFCellStyle) workbook.createCellStyle();//自动换行headerStyle.setWrapText(true);//设置水平对齐方式 居中headerStyle.setAlignment(HorizontalAlignment.CENTER);// 设置垂直对齐方式 居中headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);//颜色填充headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);headerStyle.setFillForegroundColor(bgColor);//边框 上 左 下 右headerStyle.setBorderTop(BorderStyle.THICK);headerStyle.setTopBorderColor(IndexedColors.GREY_25_PERCENT.getIndex());headerStyle.setBorderLeft(BorderStyle.THICK);headerStyle.setLeftBorderColor(IndexedColors.GREY_25_PERCENT.getIndex());headerStyle.setBorderBottom(BorderStyle.THICK);headerStyle.setBottomBorderColor(IndexedColors.GREY_25_PERCENT.getIndex());headerStyle.setBorderRight(BorderStyle.THICK);headerStyle.setRightBorderColor(IndexedColors.GREY_25_PERCENT.getIndex());//字体XSSFFont xssfFont = (XSSFFont) workbook.createFont();//颜色xssfFont.setColor(fontColor);//加粗xssfFont.setBold(true);//字体大小xssfFont.setFontHeightInPoints((short) 12);//字体xssfFont.setFontName("宋体");//font = new Font("宋体", Font.PLAIN, 14);headerStyle.setFont(xssfFont);}if (contendStyle == null) {contendStyle = (XSSFCellStyle) workbook.createCellStyle();//自动换行contendStyle.setWrapText(true);//设置水平对齐方式 居中contendStyle.setAlignment(HorizontalAlignment.CENTER);// 设置垂直对齐方式 居中contendStyle.setVerticalAlignment(VerticalAlignment.CENTER);}}@Overridepublic void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {super.afterCellDispose(writeSheetHolder, writeTableHolder, cellDataList, cell, head, relativeRowIndex, isHead);this.setColumnWidth(writeSheetHolder, cellDataList, cell, head, relativeRowIndex, isHead);if (isHead) {//设置行高 pt(磅) 1pt = 20 twips(poi 行高单位(缇))cell.getRow().setHeight((short) (20 * 33));}}@Overrideprotected void setHeadCellStyle(Cell cell, Head head, Integer integer) {cell.setCellStyle(headerStyle);}@Overrideprotected void setContentCellStyle(Cell cell, Head head, Integer integer) {cell.setCellStyle(contendStyle);}public void setColumnWidth(WriteSheetHolder writeSheetHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {boolean needSetWidth = isHead || !CollectionUtils.isEmpty(cellDataList);if (needSetWidth) {Map<Integer, Integer> maxColumnWidthMap = this.cache.computeIfAbsent(writeSheetHolder.getSheetNo(), k -> new HashMap(16));Integer columnWidth = this.dataLength(cellDataList, cell, isHead);if (columnWidth >= 0) {if (columnWidth > MAX_COLUMN_WIDTH) {columnWidth = MAX_COLUMN_WIDTH;}Integer maxColumnWidth = maxColumnWidthMap.get(cell.getColumnIndex());if (maxColumnWidth == null || columnWidth > maxColumnWidth) {maxColumnWidthMap.put(cell.getColumnIndex(), columnWidth);//设置列宽度 列宽单位为一个字符宽度的1/256writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), columnWidth);}}}}private Integer dataLength(List<CellData> cellDataList, Cell cell, Boolean isHead) {if (isHead) {return getWidth(cell.getStringCellValue());} else {CellData cellData = cellDataList.get(0);CellDataTypeEnum type = cellData.getType();if (type == null) {return -1;} else {switch (type) {case STRING:return  getWidth(cellData.getStringValue());case BOOLEAN:return cellData.getBooleanValue().toString().length() * 256;case NUMBER:return cellData.getNumberValue().toString().length() * 256;default:return -1;}}}}/*** 计算字符串宽度*/public Integer getWidth(String text){if (StrUtil.isEmpty(text)){return 0;}//由于中文和字母 数字所占字符位不一样所以采用分开计算,先用正则统计字符串中文个数,再求出非中文个数,其中中文按两个字符计算。//计算中文个数int count = RegUtils.calculateChineseNumber(text);//计算所需宽度return (count+1)*2*256 + (text.length()-count) * 256;}
}

统计字符串中的中文个数工具类:

package com.xxx.project.utils;import cn.hutool.core.util.StrUtil;import java.util.Arrays;/*** @Author zhaochun* @Date 2022年06月23日 09:32* @Description**/
public class RegUtils {/*** 中文正则*/public static String chineseRegEx = "[\\u4e00-\\u9fa5]";/****统计字符串中 中文个数*@author zhaochun*@date 2022年06月23日 09:46:26* @param str* @return int*/public static int calculateChineseNumber(String str) {if (StrUtil.isEmpty(str)) {return 0;}return (int) Arrays.stream(str.split("")).filter(i -> i.matches(chineseRegEx)).count();}
}

3.Excel导出工具类(ExcelUtil.java)

由于代码太长,为了更好说明。工具类(ExcelUtil.java)使用到的常量和方法分开展示。

/*** @Author zhaochun* @Date 2022年06月21日 14:58* @Description EXCEL导出**/
@Slf4j
public class ExcelUtil {
...
}

使用到的常量


private static final String DATA_FORMAT = "yyyy-MM-dd-HH-mm-ss";private static final String CHARACTER_UTF_8 = "UTF-8";private static final String CONTENT_TYPE_SHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";private static final String CONTENT_TYPE_MS = "application/vnd.ms-excel";private static final String CONTENT_TYPE_DOWNLOAD = "application/x-download;charset=utf-8";private static final String CONTENT_DISPOSITION = "Content-Disposition";private static final String CACHE_CONTROL = "Cache-Control";private static final String NO_STORE = "no-store";private static final String Access_Control_Expose_Headers = "Access-Control-Expose-Headers";private static final String MAX_AGE = "max-age=0";public static String EXCEL_XLSX = "xlsx";public static String EXCEL_XLX = "xls";public static String ZIP = "zip";

方法:

/*** 导出excel/zip 并写到输出流** @param response 输出流* @param fileName 导出文件名称* @param clazz    easyExcel 数据模板类* @param dataList 导出的数据(key 为文件名/sheet名,value 需要导出的数据集合)* @param outZip   是否需要压缩 ture 时 根据dataList的key生成多个excel并压缩成zip 输入到输出流,*                 false时 生成一个excel 根据dataList的key生成多个sheet入到输出流* @author zhaochun* @date 2022年06月21日 14:08:53*/public static <T> void downLoad(HttpServletResponse response, String fileName, Class<T> clazz, Map<String, List<T>> dataList, boolean outZip) {if (outZip){fileName = StrUtil.endWith(fileName,"."+ZIP)?fileName:fileName+"."+ZIP;response.setContentType(CONTENT_TYPE_DOWNLOAD);}else {fileName = StrUtil.endWith(fileName,"."+EXCEL_XLSX)?fileName:fileName+"."+EXCEL_XLSX;//设置文件类型response.setContentType(CONTENT_TYPE_MS);//设置编码格式response.setCharacterEncoding(CHARACTER_UTF_8);}//让前端取到文件名称,浏览器默认隐藏CONTENT_DISPOSITION请求头,告诉浏览器Access_Control_Expose_Headers 设置的字段不隐藏response.setHeader(Access_Control_Expose_Headers, "filename");response.setHeader("filename",URLUtil.encode(fileName, "UTF-8"));response.setHeader(CONTENT_DISPOSITION,"attachment;filename=" +  URLUtil.encode(fileName, "UTF-8"));//发送一个报头,告诉浏览器当前页面不进行缓存,每次访问的时间必须从服务器上读取最新的数据response.setHeader(CACHE_CONTROL, NO_STORE);response.addHeader(CACHE_CONTROL, MAX_AGE);try (ServletOutputStream out = response.getOutputStream()){downLoad(out,clazz, dataList,outZip);} catch (IOException e) {log.error("fileName={},e={}",fileName,e);}}/*** 导出excel/zip 并写到输出流** @param out      输出流* @param clazz    easyExcel 数据模板类* @param dataList 导出的数据(key 为文件名/sheet名,value 需要导出的数据集合)* @param outZip   是否需要压缩 ture 时 根据dataList的key生成多个excel并压缩成zip 输入到输出流,*                 false时 生成一个excel 根据dataList的key生成多个sheet入到输出流* @author zhaochun* @date 2022年06月21日 14:08:53*/public static <T> void downLoad(OutputStream out, Class<T> clazz, Map<String, List<T>> dataList, boolean outZip) {if (outZip) {downLoadExcelZip(out, clazz, dataList);} else {downLoadExcel(out, clazz, dataList);}}/*** 导出单个excel 并写到输出流** @param out      输出流* @param clazz    easyExcel 数据模板类* @param dataList 导出的数据(key sheet名,value 需要导出的数据集合)*/public static <T> void downLoadExcel(OutputStream out, Class<T> clazz, Map<String, List<T>> dataList) {
//一个excelWriter 生成一个excel
//注册自定义拦截器ExcelWriter excelWriter = EasyExcel.write(out).registerWriteHandler(new CustomCellWriteUtil()).build();dataList.forEach((k, v) -> {WriteSheet sheet = EasyExcel.writerSheet(k).head(clazz).build();excelWriter.write(v, sheet);});excelWriter.finish();}/*** 导出excel zip 并写到输出流** @param out      输出流* @param clazz    easyExcel 数据模板类* @param dataList 导出的数据(key excel名,value 需要导出的数据集合)*/public static <T> void downLoadExcelZip(OutputStream out, Class<T> clazz, Map<String, List<T>> dataList) {try (ZipOutputStream zipOutputStream = new ZipOutputStream(out)) {dataList.forEach((k, v) -> {
//用ByteArrayOutputStream 中转一下数据输出流,不然doWrite方法或finish方法会关闭流,下一次循环会报错。数据量小用着没问题;数据量大还没有想到好方法;try (ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();) {EasyExcel.write(arrayOutputStream, clazz).sheet(k).registerWriteHandler(new CustomCellWriteUtil()).doWrite(v);String fileName = StrUtil.endWith(k,"."+EXCEL_XLSX)?k:k+"."+EXCEL_XLSX;zipOutputStream.putNextEntry(new ZipEntry(fileName));zipOutputStream.write(arrayOutputStream.toByteArray());} catch (IOException e) {log.error("导出excel到zip ={},错误:{}", k, e);}});zipOutputStream.flush();} catch (IOException e) {log.error("导出excel zip 并写到输出流,刷新流错误:", e);}}

总结

easyExcel底层大量对poi接口封装,可以使用poi的方法操作Excel。

最后感谢文章中提到的博客、工具的作者或贡献者。你们的无私奉献,方便了大家。

EasyExcel 下载demo(zip压缩包)相关推荐

  1. java批量下载生成zip压缩包

    设计思路: 1.本地先创建一个zip文件 2.将批量下载的文件依次放入zip文件中 3.将zip文件返回给前端 //一.本地先生成zip文件//要批量下载的文件id数组 String[] ids = ...

  2. Android文件Apk下载变ZIP压缩包解决方案

    [root@ conf]# pwd /alidata/server/nginx/conf [root@ conf]# vi mime.typesapplication/vnd.android.pack ...

  3. SharePoint 2010 自定义Ribbon实现文档批量下载为Zip文件

    在SharePoint 2010文档库中,结合单选框,在Ribbon中提供了批量处理文档的功能,比如,批量删除.批量签出.批量签入等,但是,很遗憾,没有提供批量下载,默认的只能一个个下载,当选择多个文 ...

  4. easyExcel导出excel文件并打包成zip压缩包下载

    文件导出 专栏收录该内容 2 篇文章0 订阅 订阅专栏 package com.business.testExcelPort; import java.io.BufferedInputStream; ...

  5. java 导出表格打包zip文件下载_asyExcel导出excel并打包成zip压缩包下载

    假期期间自己在家撸码,刚好用到了导出,导出来之后是多个文件,所以需要打成压缩包并下载来给客户.查阅了一些资料,把这段代码贴在这,相当于有个记录吧. package com.business.testE ...

  6. 通过javascript在网页端生成zip压缩包并下载

    通过javascript在网页端生成zip压缩包并下载 原文:通过javascript在网页端生成zip压缩包并下载 zip.js是什么 zip.js的github项目地址:http://gildas ...

  7. 使用 easyExcel 生成多个 excel 并打包成zip压缩包

    前言: 最近项目有个需求,需要生成多个 excel 并打包成 zip下载:由于需要生成的 excel 头字段过多,这里有96个时间段的表头,如果建一个有96个字段的实体不太好,还好 easyExcel ...

  8. python web项目导出zip文件_通过javascript在网页端生成zip压缩包并下载

    zip.js是什么 通过zip.js封装一个能在网页端生成zip文件的插件, 直接在网页中创建包含文件夹和文件的压缩包,也可以自定义名字并下载: 如何使用: 1:引用zip.js 2:引用jQuery ...

  9. java 批量下载图片并打包成zip压缩包

    项目需求: 对接第三方接口时,因为数据中存储的是图片链接,第三方审核时需要把所有图片以zip压缩包的方式传入到他们ftp服务中. 提示:以下是本篇文章正文内容,下面案例可供参考 话不多说直接看代码,案 ...

最新文章

  1. css 找到隐藏元素个数
  2. 【前端开发系列】—— 利用选择器添加内容
  3. 含根式的定积分计算_不定积分计算法则总结
  4. 熵(Entropy),交叉熵(Cross-Entropy),KL-松散度(KL Divergence),似然(Likelihood)
  5. JAVA随机数之多种方法从给定范围内随机N个不重复数
  6. (译)如何使用cocos2d制作基于tile地图的游戏教程:第一部分
  7. Python 最强编辑器PyCharm详细使用指南!
  8. codeforces 598C C. Nearest vectors(极角排序)
  9. 云服务器里可以放多少网站,一台云服务器上可以放多少个网站
  10. 《数字电子技术基础》3.3 CMOS门电路(下)
  11. 用计算机解决对长江水源治理的问题,科学调控长江水资源的思考
  12. 【北大】计算机课程资料
  13. 可见光通信系统的调制效率
  14. 从免费共享经济到知识付费系统,都是如何抓住时代风口的
  15. 常用激活函数及其原理/应用,以及相关的问题
  16. ORA-00204报错
  17. 电销系统不仅外显手机号,还能自选归属地
  18. Graph-les-miserables 可视化关系图
  19. 基于OpenCV 实现银行卡数字识别
  20. Hbase基本shell操作

热门文章

  1. MATLAB角谱传播实现!FFT2光斑实际尺寸是多少?
  2. 取excel内容相同的值,并写到另一个excel表格内
  3. 河北科大计算机专业,河北科技大学
  4. python argsparse_如何创建Python命名空间(argparse.parse_args值)?
  5. 移动端网页特效移动端轮播图
  6. 基于51单片机的人体反应速度测试仪 (课设)
  7. 单片机产业城市前十排名
  8. java计算机毕业设计腾讯网游辅助小助手源码+系统+数据库+lw文档+mybatis+运行部署
  9. 【Windows】解决windows系统时间与北京时间相差8小时
  10. 三星电脑如何装linux系统软件,三星怎么在 Linux 操作系统下安装驱动程序