Java-多Excel压缩zip文件导出

一、需求目标

  1. 通过poi生成多个Excel文件
  2. 需要把已生成的Excel文件压缩成ZIP文件
  3. 支持客户客户端下载

二、解决方案

  • 单线程生成Excel,单线程压缩文件
  • 多线程生成Excel,单线程压缩文件

三、代码实例

1、pom.xml配置

<!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>3.13</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>3.13</version>
</dependency>

<!-- IO工具包 API博客参考:https://blog.csdn.net/backbug/article/details/99572931-->
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.5</version>
</dependency>

2、客户端代码

/**
 * 导出
 */
$(function () {
    $("#exportButon").on("click", function () {
        if(verifyDate() && verifyNull()){
            //window.location.href = "${base}/zip";
            window.location.href = "${base}/pool/zip";
        }
        icont++;
    })
});

3、Excel工具类

package com.tangsm.demo.util;

import java.awt.Color;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFColor;
import org.apache.poi.xssf.usermodel.XSSFFont;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

/**
 * Excel工具类
 * 
 * @author tangyb
 * @date 2020/04/21
 */
public class ExcelUtils {
	/**
	 * 表格标题单元格样式
	 * 
	 * @param workbook
	 * @param style
	 * @return
	 */
	public static XSSFCellStyle createHeadStyle(XSSFWorkbook workbook) {
		XSSFCellStyle style = workbook.createCellStyle();
		// 设置背景色
		style.setFillForegroundColor(new XSSFColor(new Color(87, 163, 250)));
		style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
		// 设置边框
		style.setBorderBottom(XSSFCellStyle.BORDER_THIN); // 下边框
		style.setBorderRight(XSSFCellStyle.BORDER_THIN);// 右边框
		style.setBorderLeft(XSSFCellStyle.BORDER_THIN);// 左边框
		style.setBorderTop(XSSFCellStyle.BORDER_THIN);// 上边框
		// 设置单元格的中心水平对齐-居中
		style.setAlignment(HSSFCellStyle.ALIGN_CENTER);
		// 设置单元格的垂直对齐类型-居中
		style.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);
		// 生成字体
		XSSFFont font = workbook.createFont();
		// 设置字体类型
		font.setFontName("微软雅黑");
		// 设置字体大小
		font.setFontHeightInPoints((short) 10);
		// 粗体字体
		font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);
		// 把字体应用到当前的样式
		style.setFont(font);
		return style;

	}

	/**
	 * 表格主体单元格样式
	 * 
	 * @param workbook
	 * @param style
	 * @return
	 */
	public static XSSFCellStyle createBodyStyle(XSSFWorkbook workbook) {
		XSSFCellStyle style = workbook.createCellStyle();
		// 设置边框
		style.setBorderBottom(XSSFCellStyle.BORDER_THIN); // 下边框
		style.setBorderRight(XSSFCellStyle.BORDER_THIN);// 右边框
		style.setBorderLeft(XSSFCellStyle.BORDER_THIN);// 左边框
		style.setBorderTop(XSSFCellStyle.BORDER_THIN);// 上边框
		// 生成字体
		XSSFFont font = workbook.createFont();
		// 字体类型
		font.setFontName("微软雅黑");
		// 设置字体大小
		font.setFontHeightInPoints((short) 9);
		// 普通字体
		font.setBoldweight(HSSFFont.BOLDWEIGHT_NORMAL);
		// 把字体应用到当前的样式
		style.setFont(font);
		return style;
	}

	/**
	 * 创建Excel工作薄并写入数据
	 */
	public static XSSFWorkbook createExcel(String sheetName) {
		// 1.声明一个工作薄
		XSSFWorkbook workbook = new XSSFWorkbook();

		// 2.创建新工作表
		XSSFSheet sheet = workbook.createSheet(sheetName);

		// 3.设置表格默认列宽度为15个字节
		sheet.setDefaultColumnWidth(15);

		// 4.设置标题和主体单元格样式
		// 4.1生成一个标题样式
		XSSFCellStyle headerStyle = createHeadStyle(workbook);
		// 4.2生成一个主体样式
		XSSFCellStyle bodyStyle = createBodyStyle(workbook);

		/** 5.这里是你去创建表头以及对应数据的方法 dealData(); start */
		// 5.1.设置首行标题
		Row row = sheet.createRow((short) 0);
		// 声明Excel标题名称
		String[] excelHeader = { "标题1", "标题2", "标题3", "标题4" };
		// 创建标题首行列
		for (int i = 0; i < excelHeader.length; i++) {
			Cell cell = row.createCell(i); // 列下标
			cell.setCellValue(excelHeader[i]); // 列值
			cell.setCellStyle(headerStyle); // 设置单元格样式
		}

		// 5.2.填充当前数据行列,从下标1跳过标题行开始
		for (int j = 1; j < 10; j++) {
			row = sheet.createRow((short) j);
			Cell cell;
			for (int k = 0; k < 4; k++) {
				cell = row.createCell(k);
				cell.setCellValue("行" + j + "-数据" + (k + 1));
				cell.setCellStyle(bodyStyle);
			}
		}
		/** 5.这里是你去创建表头以及对应数据的方法 dealData(); end */

		return workbook;
	}
}

4、单线程生成Excel,单线程压缩文件

package com.tangsm.demo.controller;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.tangsm.demo.util.ExcelUtils;

/**
 * 单线程实现多Excel压缩zip文件导出
 * 
 * @author tangyb
 * @date 2020/04/21
 */
@Controller
public class ExportZipController {

	private Logger logger = LoggerFactory.getLogger(getClass());

	/**
	 * 导出多个Excel压缩成的ZIP文件
	 * 
	 * @param response
	 * @throws IOException
	 */
	@ResponseBody
	@RequestMapping(value = "/zip", produces = "text/html;charset=UTF-8")
	public void zip(HttpServletResponse response) throws IOException {
		// 字节数组输出流,用于返回压缩后的输出流字节数组
		ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
		// ZIP压缩输出流
		ZipOutputStream zip = new ZipOutputStream(byteOut);
		// Servlet输出流
		OutputStream servletOut = response.getOutputStream();

		try {
			for (int i = 1; i < 11; i++) {
				// 创建Excel工作薄并写入数据
				XSSFWorkbook workbook = ExcelUtils.createExcel("Sheet1");
				// 写入ZIP文件条目并指定名称
				zip.putNextEntry(new ZipEntry("test/订单" + i + ".xlsx"));
				// 字节数组输出流,用于转存Excel工作薄字节
				ByteArrayOutputStream bos = new ByteArrayOutputStream();
				// Excel工作薄写入字节数组输出流
				workbook.write(bos);
				// 字节数组输出流的完整内容写入zip
				bos.writeTo(zip);

				// 刷新压缩输出流
				zip.flush();
				// 关闭当前的ZIP条目并定位流以写入下一个条目
				zip.closeEntry();

				IOUtils.closeQuietly(bos);
				IOUtils.closeQuietly(workbook);
			}
			
			// 注:转换byte[]前必须关闭zip流
			IOUtils.closeQuietly(zip);
			
			// 转为字节数组
			byte[] data = byteOut.toByteArray();

			// 当前日期
			String currentDate = nowDate();
			// 设置文件名称
			String fileName = String.format("test-%s", currentDate);
			// 设置文件名称编码格式,避免中文乱码
			fileName = new String(fileName.getBytes("UTF-8"), "ISO8859-1");

			// 清除缓冲区数据
			response.reset();
			// 设置下载方式,文件名称
			response.setHeader("Content-Disposition", "attachment; filename=" + fileName + ".zip");
			// 设置文件下载总大小,前端可以通过这个显示下载进度条
			response.addHeader("Content-Length", "" + data.length);
			// 内容的内容类型及编码格式:application/octet-stream[二进制流]
			response.setContentType("application/octet-stream; charset=UTF-8");

			// 把ZIP字节数组写入到Servlet输出流
			IOUtils.write(data, servletOut);
		} catch (Exception e) {
			logger.error("系统异常", e);
		} finally {
			// 关闭流
			IOUtils.closeQuietly(byteOut);
			IOUtils.closeQuietly(servletOut);
		}
	}

	/**
	 * 获取当前日期,格式yyyyMMdd
	 * 
	 * @return
	 */
	private String nowDate() {
		// 添加当前时间
		Date now = new Date();
		SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
		return dateFormat.format(now);
	}
}

5、多线程生成Excel,单线程压缩文件

(1)Excel处理任务-ExcelTask.java
package com.tangsm.demo.util;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.RecursiveTask;

import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Excel线程任务
 * 
 * @author tangyb
 * @date 2020/04/21
 */
public class ExcelTask extends RecursiveTask<List<XSSFWorkbook>> {

	/**
	 * 
	 */
	private static final long serialVersionUID = 4388520935732444679L;

	private Logger logger = LoggerFactory.getLogger(getClass());

	/**
	 * 每个线程阈值
	 */
	private static final int THRESHOLD = 50;

	/**
	 * 处理的数据集合
	 */
	private final List<String> dataList;

	/**
	 * 初始化类并赋值
	 * 
	 * @param dataList
	 */
	public ExcelTask(List<String> dataList) {
		this.dataList = dataList;
	}

	/**
	 * @see java.util.concurrent.RecursiveTask#compute()
	 */
	@Override
	protected List<XSSFWorkbook> compute() {
		// 数据大小
		int size = dataList.size();
		logger.info("子线程处理数据大小:{}", size);

		if (size <= THRESHOLD) {
			return process(); // 大小等于阈值顺序计算结果
		}

		// 第一个任务分组
		ExcelTask ltTask = new ExcelTask(dataList.subList(0, size / 2));
		// 第二个任务分组
		ExcelTask rtTask = new ExcelTask(dataList.subList(size / 2, size));

		// 两个任务并发执行起来
		invokeAll(ltTask, rtTask);

		List<XSSFWorkbook> allList = new ArrayList<XSSFWorkbook>();

		// 读取第一个任务分组处理的结果
		List<XSSFWorkbook> ltResult = ltTask.join();
		// 读取第二个任务分组处理的结果
		List<XSSFWorkbook> rtResult = rtTask.join();

		// 合并结果集合
		allList.addAll(ltResult);
		allList.addAll(rtResult);

		return allList;
	}

	/**
	 * 业务实现方法
	 * 
	 * @return
	 */
	private List<XSSFWorkbook> process() {
		List<XSSFWorkbook> wbList = new ArrayList<XSSFWorkbook>();
		for (String sheetName : dataList) {
			XSSFWorkbook workbook = ExcelUtils.createExcel(sheetName);
			wbList.add(workbook);
		}
		return wbList;
	}
}
(2)Controller导出实现-ExportPoolZipController.java
package com.tangsm.demo.controller;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.tangsm.demo.util.ExcelTask;

/**
 * 多线程实现多Excel压缩zip文件导出
 * @author tangyb
 * @date 2020/04/20
 */
@Controller
public class ExportPoolZipController {
    private Logger logger = LoggerFactory.getLogger(getClass());
    
    /**
     * 多线程处理ZIP
     * 
     * @param response
     * @throws IOException
     */
    @ResponseBody
    @RequestMapping(value = "/pool/zip", produces = "text/html;charset=UTF-8")
    public void poolZip(HttpServletResponse response) throws IOException {
        logger.info("多线程处理ZIP开始...");
        // 当前日期
        String currentDate = nowDate();

        // 创建主线程为4个forkJoin线程池
        ForkJoinPool pool = new ForkJoinPool(4);

        // 字节数组输出流,用于返回压缩后的输出流字节数组
        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
        
        // ZIP压缩输出流
        ZipOutputStream zip = new ZipOutputStream(byteOut);
        
        // Servlet输出流
        OutputStream servletOut = response.getOutputStream();
        
        try {
            // 需要处理的数据
            List<String> list = Arrays.asList("test1", "test2", "test3", "test4", "test6", "test6",
                    "test7", "test8", "test9", "test10");
            
            // 通过ForkJoin线程池请求处理
            ForkJoinTask<List<XSSFWorkbook>> mainTask = new ExcelTask(list);
            // 获取处理结果数据
            List<XSSFWorkbook> wbList = pool.invoke(mainTask);
            
            // 文件序号
            int i = 1;
            for (XSSFWorkbook wb : wbList) {
                // 获取sheet名称
                String sheetName = wb.getSheetName(0);
                // zip条目名称[Excel文件名称]
                String entryName = String.format("%s/%d-%s.xlsx", currentDate, i, sheetName);
                // Excel写入ZIP压缩输出流
                putEntry(entryName, zip, wb);
                i++;
            }
            // 关闭流
            IOUtils.closeQuietly(zip);
            
            // 注:转换byte[]前必须关闭zip流
            byte[] data = byteOut.toByteArray();

            // 设置导出文件名称
            String fileName = String.format("test-%s", currentDate);
            // 设置文件名称编码格式
            fileName = new String(fileName.getBytes("UTF-8"), "ISO8859-1");
            // 清除缓冲区数据
            response.reset();
            // 设置下载方式,文件名称
            response.setHeader("Content-Disposition", "attachment; filename=" + fileName + ".zip");
            // 设置文件下载总大小,前端可以通过这个显示下载进度条
            response.addHeader("Content-Length", "" + data.length);
            // 内容的内容类型及编码格式:application/octet-stream[二进制流]
            response.setContentType("application/octet-stream; charset=UTF-8");
            
            // 把ZIP字节数组写入到Servlet输出流
            IOUtils.write(data, servletOut);

        } catch (Exception e) {
            logger.error("系统异常...", e);
        } finally {
            // 关闭线程池
            pool.shutdown();
            // 关闭流
            IOUtils.closeQuietly(byteOut);
            IOUtils.closeQuietly(servletOut);
        }
    }
    
    /**
     * Excel写入zip压缩输出流
     * @param entryName
     * @param zip
     * @param workbook
     * @throws IOException 
     */
    private void putEntry(String entryName, ZipOutputStream zip,
            XSSFWorkbook workbook) throws IOException {
        // 写入ZIP文件条目并指定名称
        zip.putNextEntry(new ZipEntry(entryName));
        
        // 字节数组输出流,用于转存Excel工作薄字节
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        
        // Excel工作薄写入字节数组输出流
        workbook.write(bos);
        
        // 字节数组输出流的完整内容写入zip
        bos.writeTo(zip);
        
        // 刷新压缩输出流
        zip.flush();
        
        // 关闭当前的ZIP条目并定位流以写入下一个条目
        zip.closeEntry();
        
        IOUtils.closeQuietly(bos);
    }

	/**
	 * 获取当前日期,格式yyyyMMdd
	 * 
	 * @return
	 */
	private String nowDate() {
		// 添加当前时间
		Date now = new Date();
		SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
		return dateFormat.format(now);
	}
}

四、运行效果

img

img

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值