前言:
本工具类经过PostMan
和前端页面严格测试可用,经过了多个版本迭代优化,可以直接使用,也方便大家根据自己的业务需求,修改定制自己的导出工具。
市面上有很多封装好的导出工具(如:阿里的easyExcel,GitHub上xxl-excel等),但如果直接引用依赖,扩展性和定制性比较差,所以博主通过apache.poi
,自己实现一款Excel导出工具,方便定制使用。本工具类支持SpringMVC等主流的Java框架,支持RESTful接口,代码全部通过测试。
一.功能介绍:
- 支持多个
Excel
一次性导出并压缩成zip
包 - 支持
List<Entity>
实体类导出 - 支持
List<Map>
列数不固定的数据导出 - 支持多
Sheet
页导出 - 支持导出文件名为
URLEncode
,防止乱码 - 支持文件名、
sheet
名特殊字符自动替换 - 支持
Excel2007
以上版本 - 支持有数据的文本框描边
- 支持表头字体加大
- 表头数据单元格内换行
- 支持标题栏
- 支持控制
null
空字段是否导出 - 支持单元格将日期格式数据转换为自定义格式的时间字符串
- 支持单元格类型区分数值、字符串,且单元格类型不同,单元格对齐方式不同。
- 支持将
Excel
导出到HttpServletResponse
流中(用做提供Api接口)
二.导出工具类源码:
注: 此工具类代码可直接在项目中使用。
package com.xxx.util;
import com.xxx.util..ZipUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.*;
import org.springframework.util.ObjectUtils;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 导出Excel工具类
*
* @author xuchao
*/
@Slf4j
public class ExportExcelUtil<T> {
// 新版Excel文件后缀
private static final String EXCEL_SUFFIX = ".xlsx";
/**
* 导出多Sheet的Excel到HttpServletResponse流中(注:字段定义顺序需跟表头完全一致)
*
* (导出Excel格式:表头内容居中,字体略大于正文,颜色深灰色。正文文本类型对齐方式居左,数字类型对齐方式居右。仅有数据的单元格,有边框环绕,实体类的属性顺序即为表头顺序)
*
* @param fileName Excel文件名
* @param sheetNames 多个Sheet工作表的名称列表(不可重复)
* @param titleList 多个Sheet的标题名称列表(没有标题,则传null)
* @param headersList 多个Sheet的表头列表
* @param dataLists 多个Sheet的数据源
* @param response Http响应
* @param pattern 时间类型数据的格式,默认:yyyy-MM-dd HH:mm:ss
* @param isExportNullField 空字段是否导出(true:导出,false:不导出)
* @see ExportExcelUtilDemo 本方法使用示例
*/
public static <T> void exportExcel(String fileName, List<String> sheetNames, List<String> titleList,
List<List<String>> headersList, List<List<T>> dataLists, HttpServletResponse response, String pattern,
boolean isExportNullField) {
XSSFWorkbook wb = exportAllExcel(sheetNames, titleList, headersList, dataLists, pattern, isExportNullField);
setResponseHeader(response, replaceSpecialCharacter(fileName));
ServletOutputStream out = null;
try {
out = response.getOutputStream();
wb.write(out);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
out.flush();
out.close();
wb.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 导出多Sheet动态列的Excel到HttpServletResponse流中(注:字段定义顺序需跟表头完全一致)
*
* @param fileName Excel文件名
* @param sheetNames 多个Sheet工作表的名称列表(不可重复)
* @param titleList 多个Sheet的标题名称列表(没有标题,则传null)
* @param headersList 多个Sheet的表头列表
* @param dataLists 多个Sheet的数据源
* @param response Http响应
* @param pattern 时间类型数据的格式,默认:yyyy-MM-dd HH:mm:ss
* @param isExportNullField 空字段是否导出(true:导出,false:不导出,导出空单元格显示为"--")
*/
public static void exportDynamicExcel(String fileName, List<String> sheetNames, List<String> titleList,
List<List<String>> headersList, List<List<Map<String, Object>>> dataLists, HttpServletResponse response,
String pattern, boolean isExportNullField) {
XSSFWorkbook wb = exportDynamicExcelImpl(sheetNames, titleList, headersList, dataLists, pattern,
isExportNullField);
setResponseHeader(response, replaceSpecialCharacter(fileName));
ServletOutputStream out = null;
try {
out = response.getOutputStream();
wb.write(out);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
out.flush();
out.close();
wb.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 导出多个Excel并压缩到zip包中
*
* @param response HttpServletResponse
* @param excelList 多个Excel数据源
* @param filePath 导出路径(文件夹)
* @param zipName 压缩包名,如(ABC)
* @param pattern Excel中的时间格式
* @param isExportNullField 空字段是否导出(true:导出,false:不导出,导出空单元格显示为"--")
*/
public static void exportExcelsToZip(HttpServletResponse response, List<Map<String, Object>> excelList,
String filePath, String zipName, String pattern, boolean isExportNullField) {
setZipResponseHeader(response, replaceSpecialCharacter(zipName), true, ".zip");
long timestamp = System.nanoTime();
// 本次导出,生成一个excel文件的上级文件夹
String targetPath = filePath + File.separator + "temp" + File.separator + timestamp + File.separator + zipName;
for (Map<String, Object> excelMap : excelList) {
XSSFWorkbook wb = exportDynamicExcelImpl((List<String>) excelMap.get("sheetNames"), null,
(List<List<String>>) excelMap.get("headers"),
(List<List<Map<String, Object>>>) excelMap.get("dataLists"), pattern, isExportNullField);
// Excel输出路径示例:/usr/local/tomcat/excels/temp/1649149721911900/报表20210416/xx名称-20210416.xlsx
String excelPath =
targetPath + File.separator + replaceSpecialCharacter(excelMap.get("fileName").toString())
+ EXCEL_SUFFIX;
try {
File file = new File(excelPath);
FileUtils.forceMkdirParent(file);
FileOutputStream outputStream = new FileOutputStream(file);
wb.write(outputStream);
outputStream.close();
wb.close();
log.info("成功导出Excel:" + file.getName());
} catch (IOException e) {
log.warn("导出Excel时,创建文件出错");
e.printStackTrace();
} finally {
try {
wb.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 将所有Excel压缩,并写入到response中
ServletOutputStream out = null;
FileInputStream fileInputStream = null;
try {
// zip输出路径示例:/usr/local/tomcat/excels/temp/1649149721911900/日报20210416.zip
String outputPath =
filePath + File.separator + "temp" + File.separator + timestamp + File.separator + zipName
+ ZipUtils.ZIP_SUFFIX;
ZipUtils.compress(targetPath, outputPath);
File zipFile = new File(outputPath);
fileInputStream = new FileInputStream(zipFile);
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
out = response.getOutputStream();
byte[] bytes = new byte[bufferedInputStream.available()];
// 必须加此行代码,否则下载的Zip包损坏
int read = bufferedInputStream.read(bytes);
out.write(bytes);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
out.flush();
out.close();
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 设置响应的类型、编码和文件名称
*
* @param response
* @param fileName
*/
public static void setResponseHeader(HttpServletResponse response, String fileName) {
try {
response.reset();
response.setContentType("application/msexcel");// 设置生成的文件类型
response.setCharacterEncoding("UTF-8");// 设置文件头编码方式和文件名
// 在浏览器中测试生效,postman中文件名为response,无法修改
response.setHeader("Content-disposition", "attachment;filename=".concat(String
.valueOf(URLEncoder.encode(replaceSpecialCharacter(fileName) + EXCEL_SUFFIX, "UTF-8"))));
// 此设置,可保证web端可以取到文件名
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
// 网关服务会校验是否有"Download"标识
response.setHeader("Response-Type", "Download");
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* 设置Zip下载的响应的类型、编码和文件名称
*
* @param response http响应
* @param fileName 文件名
* @param urlEncode 是否URLEncode
* @param zipSuffix zip后缀名(默认为.zip)
*/
public static void setZipResponseHeader(HttpServletResponse response, String fileName, boolean urlEncode,
String zipSuffix) {
try {
if (zipSuffix == null) {
zipSuffix = ".zip";
}
String downloadName = urlEncode == true ?
String.valueOf(URLEncoder.encode(replaceSpecialCharacter(fileName) + zipSuffix, "UTF-8")) :
String.valueOf(replaceSpecialCharacter(fileName) + zipSuffix);
response.reset();
// 设置生成的文件类型
response.setContentType("application/x-zip-compressed");
//response.setContentType("application/octet-stream");
// 设置文件头编码方式和文件名
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=".concat(downloadName));
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
// 网关服务会校验是否有"Download"标识
response.setHeader("Response-Type", "Download");
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* 设置响应的类型、编码和文件名称
*
* @param response
* @param fileName
*/
public static void setResponseHeader(HttpServletResponse response, String fileName, boolean urlEncode) {
try {
String downloadName = urlEncode == true ?
String.valueOf(URLEncoder.encode(replaceSpecialCharacter(fileName) + EXCEL_SUFFIX, "UTF-8")) :
String.valueOf(replaceSpecialCharacter(fileName) + EXCEL_SUFFIX);
response.reset();
response.setContentType("application/msexcel");// 设置生成的文件类型
response.setCharacterEncoding("UTF-8");// 设置文件头编码方式和文件名
// 在浏览器中测试生效,postman中文件名为response,无法修改
response.setHeader("Content-Disposition", "attachment;filename=".concat(downloadName));
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
// 网关服务会校验是否有"Download"标识
response.setHeader("Response-Type", "Download");
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* 多Sheet导出实现
*
* @param sheetNames Sheet页名称列表
* @param titleList 标题列表
* @param headersList 表头列表
* @param dataLists sheet数据源列表
* @param pattern 时间格式
* @param isExportNullField 是否导出空字段
* @return
*/
private static <T> XSSFWorkbook exportAllExcel(List<String> sheetNames, List<String> titleList,
List<List<String>> headersList, List<List<T>> dataLists, String pattern, boolean isExportNullField) {
// 创建一个工作薄
XSSFWorkbook workbook = new XSSFWorkbook();
for (int i = 0; i < dataLists.size(); i++) {
// 创建一个工作表
XSSFSheet sheet = workbook.createSheet(replaceSpecialCharacter(sheetNames.get(i)));
// 设置单元格列宽度为16个字节
sheet.setDefaultColumnWidth((short) 16);
// 创建表头样式
XSSFCellStyle headersStyle = workbook.createCellStyle();
headersStyle.setBorderTop(BorderStyle.THIN);
headersStyle.setBorderBottom(BorderStyle.THIN);
headersStyle.setBorderLeft(BorderStyle.THIN);
headersStyle.setBorderRight(BorderStyle.THIN);
// 表头内容对齐方式:居中
headersStyle.setAlignment(HorizontalAlignment.CENTER);
XSSFFont headersFont = workbook.createFont();
// 设置字体格式
headersFont.setColor(new XSSFColor(java.awt.Color.DARK_GRAY));
headersFont.setFontHeightInPoints((short) 14);
// 表头样式应用生效
headersStyle.setFont(headersFont);
XSSFCellStyle dataSetStyle = workbook.createCellStyle();
// 正文单元格边框样式
dataSetStyle.setBorderBottom(BorderStyle.THIN);
dataSetStyle.setBorderRight(BorderStyle.THIN);
dataSetStyle.setBorderLeft(BorderStyle.THIN);
// 数据内容对齐方式:居左
// dataSetStyle.setAlignment(HorizontalAlignment.CENTER_SELECTION);
XSSFFont dataSetFont = workbook.createFont();
// 正文字体颜色
dataSetFont.setColor(new XSSFColor(java.awt.Color.BLACK));
// 为正文设置样式
dataSetStyle.setFont(dataSetFont);
// 获取当前Sheet页的表头
List<String> headers = headersList.get(i);
int index = 0;
if (!ObjectUtils.isEmpty(titleList)) {
String titleName = titleList.get(i);
if (!ObjectUtils.isEmpty(titleName)) {
XSSFCellStyle titleStyle = workbook.createCellStyle();
// 将首行合并居中作为标题栏
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, headers.size() - 1));
XSSFFont titleFont = workbook.createFont();
// 设置标题字体大小
titleFont.setFontHeightInPoints((short) 20);
// 设置标题字体样式
titleStyle.setFont(titleFont);
// 创建标题行并设置样式
XSSFRow titleRow = sheet.createRow(0);
XSSFCell titleCell = titleRow.createCell(0);
titleCell.setCellStyle(titleStyle);
titleCell.setCellValue(titleName);
index = 1;
}
}
// 创建表头并设置样式
XSSFRow row = sheet.createRow(index);
for (short j = 0; j < headers.size(); j++) {
XSSFCell cell = row.createCell(j);
cell.setCellStyle(headersStyle);
XSSFRichTextString text = new XSSFRichTextString(headers.get(j));
cell.setCellValue(text);
}
// 导出正文数据,并设置其样式
Iterator<?> it = dataLists.get(i).iterator();
while (it.hasNext()) {
index++;
row = sheet.createRow(index);
Object entity = it.next();
// 利用反射,根据实体类属性的先后顺序,动态调用其getXxx()方法,得到属性值
Field[] fields = entity.getClass().getDeclaredFields();
for (short k = 0; k < fields.length; k++) {
XSSFCell cell = row.createCell(k);
Field field = fields[k];
String fieldName = field.getName();
String getMethodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
try {
@SuppressWarnings("rawtypes")
Class entityClass = entity.getClass();
@SuppressWarnings("unchecked")
Method getMethod = entityClass.getMethod(getMethodName, new Class[] {});
Object value = getMethod.invoke(entity, new Object[] {});
String textValue = null;
// 如果是时间类型,格式化
if (value instanceof Date) {
Date date = (Date) value;
pattern = pattern == null || pattern.equals("") ? "yyyy-MM-dd HH:mm:ss" : pattern;
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
textValue = sdf.format(date);
} else {
// 若字段为空且允许导出空字段,则将null导出为""
textValue = value == null && isExportNullField ? "" : value.toString();
}
if (!textValue.equals("")) {
// 有数据时边框环绕
cell.setCellStyle(dataSetStyle);
// 正则判断是否为数值
Pattern p = Pattern.compile("^\\d+(\\.\\d+)?$");
Matcher matcher = p.matcher(textValue);
if (matcher.matches()) {
// 是数字当作double处理,整型也不会补充小数点
cell.setCellValue(Double.parseDouble(textValue));
} else {
// 不是数字类型作为文本输出
cell.setCellValue(textValue);
}
}
} catch (SecurityException | NoSuchMethodException | IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
return workbook;
}
/**
* 多Sheet导出动态列到Excel实现(数据源为Map)
*
* @param sheetNames Sheet页名称列表
* @param titleList 标题列表
* @param headersList 表头列表
* @param dataLists sheet数据源列表
* @param pattern 时间格式
* @param isExportNullField 是否导出空字段
* @return XSSFWorkbook workbook
*/
private static XSSFWorkbook exportDynamicExcelImpl(List<String> sheetNames, List<String> titleList,
List<List<String>> headersList, List<List<Map<String, Object>>> dataLists, String pattern,
boolean isExportNullField) {
// 创建一个工作薄
XSSFWorkbook workbook = new XSSFWorkbook();
for (int i = 0; i < dataLists.size(); i++) {
// 创建一个工作表
XSSFSheet sheet = workbook.createSheet(replaceSpecialCharacter(sheetNames.get(i)));
// 设置单元格列宽度为16个字节
sheet.setDefaultColumnWidth((short) 16);
// 创建表头样式
XSSFCellStyle headersStyle = workbook.createCellStyle();
headersStyle.setBorderTop(BorderStyle.THIN);
headersStyle.setBorderBottom(BorderStyle.THIN);
headersStyle.setBorderLeft(BorderStyle.THIN);
headersStyle.setBorderRight(BorderStyle.THIN);
// 表头内容对齐方式:居中
headersStyle.setAlignment(HorizontalAlignment.CENTER);
XSSFFont headersFont = workbook.createFont();
// 设置字体格式
headersFont.setColor(new XSSFColor(java.awt.Color.DARK_GRAY, new DefaultIndexedColorMap()));
headersFont.setFontHeightInPoints((short) 12);
// 表头样式应用生效
headersStyle.setFont(headersFont);
// 设置单元格内内容换行
headersStyle.setWrapText(true);
XSSFCellStyle dataSetStyle = workbook.createCellStyle();
// 正文单元格边框样式
dataSetStyle.setBorderBottom(BorderStyle.THIN);
dataSetStyle.setBorderRight(BorderStyle.THIN);
dataSetStyle.setBorderLeft(BorderStyle.THIN);
// 数据内容对齐方式:居左
// dataSetStyle.setAlignment(HorizontalAlignment.CENTER_SELECTION);
XSSFFont dataSetFont = workbook.createFont();
// 正文字体颜色
dataSetFont.setColor(new XSSFColor(java.awt.Color.BLACK, new DefaultIndexedColorMap()));
// 为正文设置样式
dataSetStyle.setFont(dataSetFont);
// 获取当前Sheet页的表头
List<String> headers = headersList.get(i);
int index = 0;
if (!ObjectUtils.isEmpty(titleList)) {
String titleName = titleList.get(i);
if (!ObjectUtils.isEmpty(titleName)) {
XSSFCellStyle titleStyle = workbook.createCellStyle();
// 将首行合并居中作为标题栏
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, headers.size() - 1));
XSSFFont titleFont = workbook.createFont();
// 设置标题字体大小
titleFont.setFontHeightInPoints((short) 20);
// 设置标题字体样式
titleStyle.setFont(titleFont);
// 创建标题行并设置样式
XSSFRow titleRow = sheet.createRow(0);
XSSFCell titleCell = titleRow.createCell(0);
titleCell.setCellStyle(titleStyle);
titleCell.setCellValue(titleName);
index = 1;
}
}
// 创建表头并设置样式
XSSFRow row = sheet.createRow(index);
for (short j = 0; j < headers.size(); j++) {
XSSFCell cell = row.createCell(j);
cell.setCellStyle(headersStyle);
XSSFRichTextString text = new XSSFRichTextString(headers.get(j));
cell.setCellValue(text);
}
// 导出正文数据,并设置其样式
ListIterator<Map<String, Object>> it = dataLists.get(i).listIterator();
while (it.hasNext()) {
try {
index++;
row = sheet.createRow(index);
Map<String, Object> map = it.next();
headers = new ArrayList<String>(map.keySet());
List<Object> values = new ArrayList<Object>(map.values());
for (int k = 0; k < map.keySet().size(); k++) {
try {
XSSFCell cell = row.createCell(k);
cell.setCellStyle(dataSetStyle);
String textValue = null;
Object value = values.get(k);
// 如果是时间类型,格式化
if (value instanceof Date) {
Date date = (Date) value;
pattern = pattern == null || pattern.equals("") ? "yyyy-MM-dd HH:mm:ss" : pattern;
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
textValue = sdf.format(date);
} else {
// 若字段为空且用户允许导出空字段,则将null导出为"--"
textValue = value == null && isExportNullField ? "--" : value.toString();
}
if (!textValue.equals("")) {
// 有数据时边框环绕
//cell.setCellStyle(dataSetStyle);
// 正则判断是否为数值
Pattern p = Pattern.compile("^\\d+(\\.\\d+)?$");
Matcher matcher = p.matcher(textValue);
if (matcher.matches()) {
// 是数字当作double处理,整型也不会补充小数点
cell.setCellValue(Double.parseDouble(textValue));
} else {
// 不是数字类型作为文本输出
cell.setCellValue(textValue);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
return workbook;
}
/**
* 用下划线替换所有特殊字符
*
* @param targetStr 目标字符串
*/
public static String replaceSpecialCharacter(String targetStr) {
if (null != targetStr && !"".equals(targetStr.trim())) {
String regEx = "[\\\\|:/\"<>?*\\[\\] ]";
Pattern p = Pattern.compile(regEx);
Matcher m = p.matcher(targetStr);
return m.replaceAll("_");
}
return null;
}
}
压缩工具类:
注: 如果不需要导出后,将Excel压缩成Zip包功能,可以不导出此类。
package com.xxx.util;
/**
* @description: 压缩工具类
* @author: 大脑补丁
* @create: 2021-04-16 15:51
*/
import java.io.*;
import java.util.Enumeration;
import java.util.zip.*;
public class ZipUtils {
static final int BUFFER = 8192;
static final String ZIP_SUFFIX = ".zip";
/**
* 压缩文件
*
* @param srcPath 要压缩的文件或文件夹(如:/usr/local/目录)
* @param zipPath 输出的zip文件路径(如:/usr/local/abc.zip)
* @throws IOException
*/
public static void compress(String srcPath, String zipPath) throws IOException {
File srcFile = new File(srcPath);
File zipFile = new File(zipPath);
if (!srcFile.exists()) {
throw new FileNotFoundException(srcPath + "不存在!");
}
FileOutputStream out = null;
ZipOutputStream zipOut = null;
try {
out = new FileOutputStream(zipFile);
CheckedOutputStream cos = new CheckedOutputStream(out, new CRC32());
zipOut = new ZipOutputStream(cos);
String baseDir = "";
compress(srcFile, zipOut, baseDir);
} finally {
if (null != zipOut) {
zipOut.close();
out = null;
}
if (null != out) {
out.close();
}
}
}
private static void compress(File file, ZipOutputStream zipOut, String baseDir) throws IOException {
if (file.isDirectory()) {
compressDirectory(file, zipOut, baseDir);
} else {
compressFile(file, zipOut, baseDir);
}
}
/** 压缩一个目录 */
private static void compressDirectory(File directory, ZipOutputStream zipOut, String baseDir) throws IOException {
File[] files = directory.listFiles();
for (int i = 0; i < files.length; i++) {
compress(files[i], zipOut, baseDir + directory.getName() + "/");
}
}
/** 压缩一个文件 */
private static void compressFile(File file, ZipOutputStream zipOut, String baseDir) throws IOException {
if (!file.exists()) {
return;
}
BufferedInputStream bis = null;
try {
bis = new BufferedInputStream(new FileInputStream(file));
ZipEntry entry = new ZipEntry(baseDir + file.getName());
zipOut.putNextEntry(entry);
int count;
byte data[] = new byte[BUFFER];
while ((count = bis.read(data, 0, BUFFER)) != -1) {
zipOut.write(data, 0, count);
}
} finally {
if (null != bis) {
bis.close();
}
}
}
/**
* 解压文件
*
* @param zipFile
* @param dstPath
* @throws IOException
*/
public static void decompress(String zipFile, String dstPath) throws IOException {
File pathFile = new File(dstPath);
if (!pathFile.exists()) {
pathFile.mkdirs();
}
ZipFile zip = new ZipFile(zipFile);
for (Enumeration entries = zip.entries(); entries.hasMoreElements(); ) {
ZipEntry entry = (ZipEntry) entries.nextElement();
String zipEntryName = entry.getName();
InputStream in = null;
OutputStream out = null;
try {
in = zip.getInputStream(entry);
String outPath = (dstPath + "/" + zipEntryName).replaceAll("\\*", "/");
;
//判断路径是否存在,不存在则创建文件路径
File file = new File(outPath.substring(0, outPath.lastIndexOf('/')));
if (!file.exists()) {
file.mkdirs();
}
//判断文件全路径是否为文件夹,如果是上面已经上传,不需要解压
if (new File(outPath).isDirectory()) {
continue;
}
out = new FileOutputStream(outPath);
byte[] buf1 = new byte[1024];
int len;
while ((len = in.read(buf1)) > 0) {
out.write(buf1, 0, len);
}
} finally {
if (null != in) {
in.close();
}
if (null != out) {
out.close();
}
}
}
zip.close();
}
public static void main(String[] args) throws Exception {
String targetFolderPath = "/Users/test/zipFile/zipFolder";
String rawZipFilePath = "/Users/test/zipFile/raw.zip";
String newZipFilePath = "/Users/test/zipFile/new.zip";
//将Zip文件解压缩到目标目录
decompress(rawZipFilePath, targetFolderPath);
//将目标目录的文件压缩成Zip文件
compress(targetFolderPath, newZipFilePath);
}
}
所需jar包依赖org.apache.poi
版本:
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.0</version>
<exclusions>
<exclusion>
<artifactId>poi</artifactId>
<groupId>org.apache.poi</groupId>
</exclusion>
<exclusion>
<artifactId>xmlbeans</artifactId>
<groupId>org.apache.xmlbeans</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.1.2</version>
</dependency>
三.数据源数据结构解释:
1.导出方法参数数据源:List<List<T>> dataLists
第一层List
:多个sheet页。
第二层List
:一个Sheet页下的多条数据。
T:实体类,对应一个Sheet页下的一行数据。
2.导出方法参数数据源:List<List<Map<String, Object>>> dataLists
第一层List
:多个Sheet页数据。
第二层List
:一个Sheet页下的多条数据。
Map<String, Object>
:一个Sheet页下的一行数据,其中key
:表头,value
:对应单元格的值。
四.功能和使用方法:
1.导出实体类方式
使用场景: 适合Excel列的名称顺序和列数是固定的业务场景。
注意事项: 实体类的属性声明的顺序,即为Excel导出后列的顺序,想调整列的顺序,需调整实体类属性声明的顺序即可。
导出方法:
/**
* 导出多Sheet的Excel到HttpServletResponse流中
*
* @param fileName
* 另存为文件名
* @param sheetName
* 工作簿中的多张Sheet工作表的名称列表
* @param titleName
* 表格的标题名称(没有标题,则传null)
* @param headers
* 表头列表
* @param dataList
* 要导出的数据源
* @param HttpServletResponse
* Http响应
* @param pattern
* 时间类型数据的格式,默认UTC格式
* @param isExportNullField
* 空字段是否导出(true:导出,false:不导出)
*/
public static <T> void exportExcel(String fileName, List<String> sheetNames, String titleName, List<String> headers,
List<List<T>> dataLists, HttpServletResponse response, String pattern, boolean isExportNullField){……}
使用示例:
实体类示例:
// Excel对应实体类(字段定义顺序,必须和表头顺序一致)
@Data
public class AlarmEventDTO implements Serializable {
// 设备类型
private String deviceTypeName;
// 设备型号
private String deviceModeName;
// 测点编号
private String pointCode;
// 告警参数
private int warningCheckRule;
// 告警描述
private String warningCheckDesc;
// 告警级别
private Integer warningLevel;
// 告警类别
private String warningType;
// 是否启用
private String warningEnabled;
// 所属部件
private String belongComponent;
}
注: 文件名和sheet名:注意不要含有Excel不支持的特殊字符。
使用示例:
在SpringMVC的RESTful接口中,控制器中调用示例
/**
* Controller层使用示例
*/
//@GetMapping(value = "/alarm/export")
public void exportAlarmEvent(HttpServletResponse response) {
// 调用你的服务,获取数据源
List<List<AlarmEventDTO>> dataLists = new ArrayList<>();
if (dataLists != null && dataLists.size() > 0) {
try {
// Sheet页名称列表
List<String> sheetNames = new ArrayList<String>();
// 一个Sheet页表头名称列表
List<String> headers = new ArrayList<String>(
Arrays.asList("设备类型", "设备型号", "测点编号", "告警参数", "告警描述", "告警级别", "告警类别", "是否启用", "所属部件"));
// 模拟多个Sheet页表头
List<List<String>> headersList = Arrays.asList(headers, headers, headers);
// 多个Sheet页标题列表(若)
List<String> titleList = new ArrayList<String>();
ExportExcelUtil
.exportExcel("Excel文件名", sheetNames, titleList, headersList, dataLists, response, null, true);
} catch (Exception e) {
log.error("导出告警事件出错", e);
}
}
}
2.导出Map对象的方式
使用场景: 适合Excel列的名称和顺序和列数是不固定的,如每次导出的列数可能不一致的场景。
注意事项: Excel导出后列的顺序,为Map中的键值对加入的顺序,要想导出后列的顺序固定,可将Map实例化为LinkedHashMap
即可使导出后的列顺序不会改变。
例: 如下方式缓存导出数据,导出后的“名称”列,会在“类型”列的左侧。
Map<String, Object> tempMap = new LinkedHashMap<String, Object>();
tempMap.put("名称", device.getName());
tempMap.put("类型", device.getDeviceType());
导出方法:
/**
* 导出多Sheet动态列的Excel到HttpServletResponse流中
*
* @param fileName 另存为文件名
* @param sheetNames 工作簿中的多张Sheet工作表的名称列表
* @param titleName 表格的标题名称(没有标题,则传null)
* @param headersList 多个sheet页的表头列表
* @param dataLists 要导出的数据源
* @param response Http响应
* @param pattern 时间类型数据的格式,默认UTC格式
* @param isExportNullField 空字段是否导出(true:导出,false:不导出)
*/
public static void exportDynamicExcel(String fileName, List<String> sheetNames, String titleName,
List<List<String>> headersList, List<List<Map<String, Object>>> dataLists, HttpServletResponse response,
String pattern, boolean isExportNullField) {……}
使用示例:
在SpringMVC的RESTful接口中,控制器中调用示例:
@PostMapping(value = "/history/export")
public void exportHistory(HttpServletResponse response, @RequestBody Params params) {
List<List<Map<String, Object>>> datalists = deviceService.exportHistory(params);
if (!datalists.isEmpty()) {
try {
List<String> headers1 = new ArrayList<String>("表头列名1","表头列名2","表头列名3");
List<String> headers2 = new ArrayList<String>("表头列名1","表头列名2","表头列名3");
String fileName ="你的文件名,若包含Excel不支持的特殊字符,会自动处理成下划线";
ExportExcelUtil.exportDynamicExcel(fileName, Arrays.asList("用户历史数据"), null, Arrays.asList(headers1 ,headers2), datalists, response,
null, true);
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.多个Excel同时导出并压缩成zip包示例
①:Seivice层组装数据源
看清楚数据结构的嵌套,组装好数据源。这里展示两个Excel同时导出的数据源结构。
public class YourService{
public List<Map<String, Object>> export (ExcelInput input){
// 多个Excel的数据源
List<Map<String, Object>> resultList = new ArrayList<>();
// 多个Sheet页数据源,每个List<Map<String, Object>>为一个Sheet页的数据源
List<List<Map<String, Object>>> deviceDataLists = new LinkedList<>();
// 第1个Excel数据源
Map<String, Object> powerExcelMap = new HashMap<>();
powerExcelMap.put("dataLists", deviceDataLists);
powerExcelMap.put("fileName", "excel文件名字1");
//多个Sheet页名字
powerExcelMap.put("sheetNames", Arrays.asList("第1个sheet页名字","第2个sheet页名字"));
// 第一个Sheet页的表头
List<String> powerHeaders1 = new LinkedList<>();
// 第二个Sheet页的表头
List<String> powerHeaders2 = new LinkedList<>();
powerExcelMap.put("headers", Arrays.asList(powerHeaders1,powerHeaders2 ));
resultList.add(powerExcelMap);
// 第2个Excel数据源
List<List<Map<String, Object>>> weatherDataLists= new LinkedList<>();
List<String> weatherHeaders = new LinkedList<>();
Map<String, Object> weatherExcelMap = new HashMap<>();
weatherExcelMap.put("dataLists", weatherDataLists);
weatherExcelMap.put("fileName", "excel文件名字2");
powerExcelMap.put("sheetNames", Arrays.asList("第1个sheet页名字","第2个sheet页名字"));
// 第一个Sheet页的表头
List<String> weatherHeaders1 = new LinkedList<>();
// 第二个Sheet页的表头
List<String> weatherHeaders2 = new LinkedList<>();
weatherExcelMap.put("headers", Arrays.asList(weatherHeaders1,weatherHeaders2));
resultList.add(weatherExcelMap);
}
}
②.Contorller层调用Excel工具类导出多个Excel到Response中
@PostMapping("/export")
public void export (HttpServletResponse response, @RequestBody ExcelInput input) {
// 获取①中的数据源
List<Map<String, Object>> excelList = yourService.export(input);
if (ObjectUtils.isEmpty(excelList) || ObjectUtils.isEmpty(input.getTime())) {
return;
}
// 导出多个Excel到zip包中
ExportExcelUtil.exportExcelsToZip(response, excelList, filePath, "压缩包名称", null, true);
}
五.导出效果测试:
1.PostMan测试:
导出多个Excel到Zip包中示例:
注意:文件名中的中文都经过URLEncode,需要请前端同学URLDecode一下,即可展示出中文名。
2.网页测试:
3.导出效果图:
看下导出的效果吧,边框、表头、对齐方式、字体大小、单元格数据换行、时间字符串格式转换等。效果满意的话点个赞吧!
Excel实用教程集锦
以下是我写的关于Java操作Excel的所有教程,基本包含了所有场景。
1.如果简单导出推荐使用工具类的方式,这种配置最简单。
2.如果对导出样式要求极高的还原度,推荐使用Freemarker方式,FreeMarker模板引擎可以通吃所有Excel的导出,属于一劳永逸的方式,项目经常导出推荐使用这种方式。
3.Freemarker导出的Excel为xml格式,是通过重命名为xls后,每次会打开弹框问题,我在《Freemarker整合poi导出带有图片的Excel教程》也已经完美解决,本教程将直接导出真正的xls格式,完美适配新版office和wps。Freemarker是无法导出带有图片的Excel,通过其他技术手段,也在本教程中完美导出带有图片的Excel。
4.下列教程中的代码都经本人和网友多次验证,真实有效!