小工具      在线工具  汉语词典  css  js  c++  java

前言:

本工具类经过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、以下教程中的代码经过本人和网友多次验证,真实有效!

↓↓↓↓一揽子Excel解决方案,赶快收藏吧↓↓↓↓

《Java导入Excel工具类使用教程》

《Java Excel导出工具使用教程》

《Freemarker导出复杂Excel图形和文本教程》

《Freemarker集成poi带图片导出Excel教程》

. . .

相关推荐

额外说明

mybatis-plus 性能分析插件

先配置 如果不让在生产环境启用 设置运行参数 效果   格式化打印语句     怎么利用插件揪出慢的SQL呢? 这肯定只能用于开发环境,不能用于生产环境        

额外说明

MUMU模拟器设置网络

1、我们先打开我们的模拟器,等待进入到主页面。 2、打开之后有两个设置选项,我们要找到的设置是模拟器里面的设置。 3、这个时候我们点击设置依次看到有一个wife的图标,点击wife。 4、点击之后我们长按信号图标会弹出一个菜单,在这个菜单下我们可以看到有

额外说明

人生的十二种财富

一、积极的精神态度:所有的财富,都始于一种内心状态,内心乃个人能完全控制的唯一的东西。就将积极的精神态度置于人生十二大财富之首。 二、良好的体格:良好的体格始于一种“健康意识”,保持正常积极的精神态度和适度的体育锻炼是避免疾病的最佳途径。 三、人际关系的

额外说明

Vue路由简单使用

1.定义导航 2.定义路由出口,路由匹配到的组件将会渲染到此处 3.定义路由(路径和组件名) 4.创建路由实例 5.挂载上一步创建的路由由实例 <!DOCTYPE html> <html lang="en"> <head> <meta chars

额外说明

【Elasticsearch】es脚本编程使用详解

目录 一、es脚本语言介绍 1.1 什么是es脚本 1.2 es脚本支持的语言 1.3 es脚本语言特点

额外说明

一文带你彻底搞懂C/C++编程中static与extern两关键字的使用

目录 1、概述 2、编译C++代码时遇到的变量及函数重复定义的问题 3、用 extern 声明外部变量

额外说明

【Javascript保姆级教程】if判断语句的三种形式

文章目录 前言 一、if语句 1.1 怎么使用 1.2 示例代码 1.3 示例代码2 二、if...else语句 2.1 怎么使用 2.2 示例代码 2.3 示例代码 三、if...else if...语句 3.1 怎么使用 3.2 示例代码 3.3 示

额外说明

2023.8.15 软件测试的概念

目录 关于需求 需求的定义: 用户需求: 软件需求: 区别: 测试人员眼里的需求 : 开展软件测试工作过程: 举例:(用户登录 ) 常见方式: 关于测试用例(CASE) 什么是测试用例: 基本概念: 举例:(牛客OJ题) 为什么有测试用例: 关于软件错误

额外说明

《Cesium 进阶知识点》- 关闭天空盒,自定义背景图

效果   关键代码 1.代码第 4 行,初始化时配置 webgl.alpha 为 true; 2.代码第 8 行,不显示天空盒; 3.代码第 9 行,背景色透明; const viewer = new Cesium.Viewer('cesiumConta

额外说明

Spring Security配置访问权限在登录页循环并报错302

事情是这样的,在学习Spring Security框架时,使用框架默认的登录页面,输入正确的账号密码后可以正常登录,但是将登录页面改为自定义页面后,就无法登录了。 如下: 密码账户密码肯定时没有问题的,看配置源码如下: 定制表格 <form ac

ads via 小工具