EasyExcel3.0.5 导出多个sheet,批量下载打包成ZIP压缩包

多sheet导出并批量下载附件

  • 前言
  • 代码实现
    • Controller
    • 接口类
    • 实现类 (重中之重)
      • 实现类重点强调
  • 测试
  • 结语

前言

最近浏览语雀社区官网,里面有一篇 easyexcel 里面 图片导出,对此,我产生了浓厚兴趣。恰巧公司有这么一个BT 需求,但是作为有追求的人,图片放在 excel 单元格是不是太不美观了,万一图片很大,那不是奇丑无比了! 有没有什么办法让图片下载到指定的本地文件路径,这样excel 既美观,又不影响图片查看。

上一次写了一篇: EasyExcel3.0.5导出多个sheet,含查询优化,竟然无人问津,这篇写的非常完整,既实现了多个 sheet 导出,也实现了附件下载。但是批量下载附件有几个问题点

  • 附件地址解析的局限性。我是存储在 OSS,OSS 返回的路径可以用流直接下载,但是如果有人不用OSS 呢,五花八门的附件 URL 防不胜防,就会导致解析出错!例如遇到下面的 URL 地址,该怎么玩?现实中就有!
https://label1.cmscentertech.com/Label/Print?coNumber=NC2022040700374618&type=MainLabel&childForecastNumber=YT2203721272081883-1&labelformat=PDF

http://yunexpress-fileupload.oss-cn-shenzhen.aliyuncs.com/eddfbbe9c142456f9984d706e10a01e4.Jpeg
  • 保存附件地址的问题。创建一个本地文件目录,貌似好像没啥毛病,但是上线部署到服务器的时候,却不能下载到本地了,因为服务器上没有 D盘、C盘,而且只会在服务器创建个目录,egg pain 吗?如下图,服务器上产生了目录并没有下载到你的本地目录。
    EasyExcel3.0.5 导出多个sheet,批量下载打包成ZIP压缩包_第1张图片

好了,遇到问题解决问题,才能帮助自己成长。话不多说,开干!

代码实现

为了让读者可以轻松读懂读通,并可以参照实现,还是贴完整一点。

Controller

package cn.com.easyExcel.controller;

import cn.com.easyExcel.param.OrderExportParam;
import cn.com.easyExcel.service.OrderService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Api(tags = "订单表")
@RestController
@RequestMapping(value = "/order")
public class OrderController {
    @Autowired
    private OrderService orderService;

    @ApiOperation(value = "多sheet导出并下载附件")
    @PostMapping(value = "moreSheetAndDownloadUrlFile")
    public void moreSheetAndDownloadUrlFile(HttpServletRequest request,
                                            HttpServletResponse response,
                                            OrderExportParam param){
        orderService.moreSheetAndDownloadUrlFile(request, response, param);
    }
}

接口类

package cn.com.easyExcel.service;

import cn.com.easyExcel.param.OrderExportParam;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public interface OrderService {
    /**
     * 多sheet导出并且下载附件
     */
    void moreSheetAndDownloadUrlFile(HttpServletRequest request, HttpServletResponse response, OrderExportParam param);
}

实现类 (重中之重)

package cn.com.easyExcel.service.impl;

import cn.com.easyExcel.excel.listener.ExportListener;
import cn.com.easyExcel.mapper.OrderDetailMapper;
import cn.com.easyExcel.mapper.OrderMapper;
import cn.com.easyExcel.param.OrderExportParam;
import cn.com.easyExcel.pojo.Order;
import cn.com.easyExcel.pojo.OrderDetail;
import cn.com.easyExcel.pojo.OrderImage;
import cn.com.easyExcel.service.OrderService;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.tomcat.util.http.fileupload.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;


@Slf4j
@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private OrderDetailMapper orderDetailMapper;

    private static final int PAGE_SIZE = 1000;

    private static final String PATTERN = "yyyy-MM-dd-HH-mm-ss";

    //需要压缩的文件夹
    private final static String DOWNLOAD_DIR = "download";

    //打包后的文件夹
    private final static String DOWNLOAD_ZIP = "downloadZip";

    DateTimeFormatter dtf = DateTimeFormatter.ofPattern(PATTERN);

    @SneakyThrows
    @Override
    public void moreSheetAndDownloadUrlFile(HttpServletRequest request,
                                            HttpServletResponse response,
                                            OrderExportParam param) {
        long startTime = System.currentTimeMillis();
        // 1.定义变量
        List<Order> allOrderList = new ArrayList<>();   //订单信息

        List<OrderDetail> allOrderDetailList = new ArrayList<>();   //订单详情信息

        List<OrderImage> allImageList = new ArrayList<>();    //订单图片信息

        List<String> allImageFileList = new ArrayList<>();    //订单图片文件

        // 2.获取数据
        LambdaQueryWrapper<Order> queryWrapper = Wrappers.lambdaQuery();
        //TODO: 组装查询(省略)

        int startIndex = 1;
        while (true){
            int startParam =(startIndex - 1) * PAGE_SIZE;
            int pageIndex = (int) Math.ceil((double) startParam / (double) PAGE_SIZE+1);
            Page<Order> pageQuery = new Page<>(pageIndex, PAGE_SIZE, false);
            Page<Order> orderListByPage = orderMapper.selectPage(pageQuery, queryWrapper);

            //订单信息
            List<Order> orderList = orderListByPage.getRecords();
            if (CollectionUtils.isEmpty(orderList)) {
                break;
            }
            allOrderList.addAll(orderList);

            //订单详情
            LambdaQueryWrapper<OrderDetail> detailWrapper = Wrappers.lambdaQuery();
            List<Long> orderIds = orderList.stream().map(Order::getOrderId).collect(Collectors.toList());
            detailWrapper.in(OrderDetail::getOrderId, orderIds);
            List<OrderDetail> orderDetailList = orderDetailMapper.selectList(detailWrapper);    //查询详情
            allOrderDetailList.addAll(orderDetailList);

            //订单图片
            List<String> imageFileList = orderDetailList.stream().map(OrderDetail::getImage).collect(Collectors.toList());
            allImageFileList.addAll(imageFileList);

            List<OrderImage> imageList = new ArrayList<>();
            for (String image : imageFileList) {
                OrderImage orderImage = OrderImage.builder().image(image).build();
                imageList.add(orderImage);
            }
            allImageList.addAll(imageList);
            startIndex++;
        }

        // 3. easyExcel组件
        String nowTime = dtf.format(LocalDateTime.now().plusHours(8));
        ExcelWriter excelWriter = null;
        if(CollUtil.isNotEmpty(allImageFileList)){
            File fileMkDir = new File(DOWNLOAD_DIR);
            if(!fileMkDir.exists()){
                fileMkDir.mkdir();
            }
            String filePath = DOWNLOAD_DIR + "/" + "Order" + nowTime + ".xlsx";
            File file = new File(filePath);
            excelWriter = EasyExcel.write(file).build();
        } else {
            ServletOutputStream outputStream = ExportListener.getServletOutputStream(response, "订单信息");
            excelWriter = EasyExcel.write(outputStream).build();
        }

        if (CollectionUtils.isNotEmpty(allOrderList)) {
            WriteSheet orderSheet = EasyExcel.writerSheet(0, "订单信息").head(Order.class).build();
            excelWriter.write(allOrderList, orderSheet);
        }
        if (CollectionUtils.isNotEmpty(allOrderDetailList)) {
            WriteSheet detailSheet = EasyExcel.writerSheet(1, "订单详情").head(OrderDetail.class).build();
            excelWriter.write(allOrderDetailList, detailSheet);
        }
        if (CollectionUtils.isNotEmpty(allImageList)) {
            WriteSheet imageSheet = EasyExcel.writerSheet(2, "订单图片").head(OrderImage.class).build();
            excelWriter.write(allImageList, imageSheet);
        }
        // 千万别忘记finish 会帮忙关闭流
        if (excelWriter != null) {
            excelWriter.finish();
        }

        // 4.下载附件
        File fileDir = null;
        if(CollUtil.isNotEmpty(allImageFileList)){
            for(String imageFile : allImageFileList){
                String fileName = nowTime + (int)((Math.random()*9+1)* 100000) + ".pdf";
                fileDir = URLDownLoad(imageFile, fileName);
            }
            downloadZip(request, response, nowTime);
        }
        //删除暂存目录
        if(null != fileDir){
            FileUtils.deleteDirectory(fileDir);
        }

        long endTime = System.currentTimeMillis();
        log.info("多sheet导出耗时:{}", endTime - startTime);
    }

    public File URLDownLoad(String urlStr, String fileName) throws IOException {
        //1. 定义URLConnection
        URL url = new URL(urlStr);
        URLConnection conn = url.openConnection();
        conn.setConnectTimeout(3 * 1000);   // 设置超时间为3秒
        conn.setRequestProperty("User-Agent","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36");

        //2. 获取文件名
        String headerField = conn.getHeaderField("Content-Disposition");
        String realFileName = null;
        if(StrUtil.isNotEmpty(headerField)){
            realFileName = URLDecoder.decode(headerField.split(";")[2].split("=")[1], "UTF-8");   //ossUrl 头部解析的文件名
        }
        String urlFile = conn.getURL().getFile();   //url 路径的文件名
        String realFile = StrUtil.isNotEmpty(realFileName) ? realFileName : urlFile;
        if (realFile != null && realFile.indexOf(".") > 0) {    //如果文件名没有后缀,用随机生成的pdf后缀
            fileName = Optional.of(realFile).orElse(fileName);
        }

        //3. 创建临时文件
        File fileDir = new File(DOWNLOAD_DIR);
        if(!fileDir.exists()){
            fileDir.mkdir();
        }
        String filePath = DOWNLOAD_DIR + "/" + fileName;
        File file = new File(filePath);
        file.createNewFile();//创建文件,存在覆盖

        FileOutputStream outputStream = new FileOutputStream(file);
        InputStream inputStream = conn.getInputStream();

        byte[] buffer = new byte[1024 * 5];
        int len = 0;
        while ((len = inputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, len);
        }
        outputStream.close();
        inputStream.close();
        return fileDir;
    }

    /**
     * 下载压缩包
     */
    public void downloadZip(HttpServletRequest request,HttpServletResponse response, String nowTime) throws IOException {
        OutputStream out = null;
        File zip = null;

        File fileDir = new File(DOWNLOAD_ZIP);
        if(!fileDir.exists()){//如果文件夹不存在
            fileDir.mkdir();//创建文件夹
        }

        //多个文件进行压缩,批量打包下载文件
        //创建压缩文件需要的空的zip包
        String zipName = "order-".concat(nowTime).concat(".zip");
        String zipFilePath = DOWNLOAD_ZIP + File.separator + zipName;
        //压缩文件
        zip = new File(zipFilePath);
        zip.createNewFile();//创建文件,存在覆盖

        //创建zip文件输出流
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zip));
        this.zipFile(DOWNLOAD_DIR, zos);
        zos.close();

        //将打包后的文件写到客户端,输出的方法同上,使用缓冲流输出
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(zipFilePath));
        byte[] buff = new byte[bis.available()];
        bis.read(buff);
        bis.close();

        //IO流实现下载的功能
        response.setCharacterEncoding("UTF-8"); //设置编码字符
        response.setContentType("application/zip");
        //防止文件名乱码
        String userAgent = request.getHeader("USER-AGENT");

        if (userAgent.contains("Firefox") || userAgent.contains("firefox")) {//火狐浏览器
            zipName = new String(zipName.getBytes(), "ISO8859-1");
        } else {
            zipName = URLEncoder.encode(zipName, "UTF-8");//其他浏览器
        }
        response.setHeader("Content-Disposition", "attachment;filename=" + zipName);//设置下载的压缩文件名称

        out = response.getOutputStream();   //创建页面返回方式为输出流,会自动弹出下载框
        out.write(buff);//输出数据文件

        //下载完后删除文件夹和压缩包
        FileUtils.deleteDirectory(fileDir);
        zip.delete();

        out.flush();//释放缓存
        out.close();//关闭输出流
    }

    /**
     * 压缩文件
     * @param filePath	需要压缩的文件夹
     * @param zos	zip文件输出流
     */
    private void zipFile(String filePath,ZipOutputStream zos) throws IOException {
        File inputFile = new File(filePath);  //根据文件路径创建文件
        if(inputFile.exists()) { //判断文件是否存在
            if (inputFile.isFile()) {  //判断是否属于文件,还是文件夹
                //创建输入流读取文件
                BufferedInputStream bis = new BufferedInputStream(new FileInputStream(inputFile));
                //将文件写入zip内,即将文件进行打包
                zos.putNextEntry(new ZipEntry(inputFile.getName()));

                //写入文件的方法,同上
                int size = 0;
                byte[] buffer = new byte[1024];  //设置读取数据缓存大小
                while ((size = bis.read(buffer)) > 0) {
                    zos.write(buffer, 0, size);
                }
                bis.close();
            } else {  //如果是文件夹,写入zip
                File[] files = inputFile.listFiles();
                if (files != null) {
                    for (File fileTem : files) {
                        zipFile(fileTem.toString(),zos);
                    }
                }
            }
        }
        //关闭输入输出流
        zos.closeEntry();
    }
}

实现类重点强调

声明:

  1. 实现类这里可以拆分几个方法,把每个步骤做的事情分开;
  2. 硬编码的部分,可以定常量,时间问题,此处省略了。

代码核心:
分析过程只截取部分重点。

多sheet导出的核心部分:
a. 要实现多个 sheet 首先声明多个集合接收各自的数据

List<Order> allOrderList = new ArrayList<>();   //订单信息

List<OrderDetail> allOrderDetailList = new ArrayList<>();   //订单详情信息

List<OrderImage> allImageList = new ArrayList<>();    //订单图片信息

b. 根据不同的对象分别 ExcelWriter

if (CollectionUtils.isNotEmpty(allOrderList)) {
    WriteSheet orderSheet = EasyExcel.writerSheet(0, "订单信息").head(Order.class).build();
     excelWriter.write(allOrderList, orderSheet);
}
if (CollectionUtils.isNotEmpty(allOrderDetailList)) {
    WriteSheet detailSheet = EasyExcel.writerSheet(1, "订单详情").head(OrderDetail.class).build();
    excelWriter.write(allOrderDetailList, detailSheet);
}
if (CollectionUtils.isNotEmpty(allImageList)) {
    WriteSheet imageSheet = EasyExcel.writerSheet(2, "订单图片").head(OrderImage.class).build();
    excelWriter.write(allImageList, imageSheet);
}

批量下载附件核心部分

a. 目录创建在项目的路径,但是要及时清空,防止累加下载附件

 //需要压缩的文件夹
 private final static String DOWNLOAD_DIR = "download";

 //打包后的文件夹
 private final static String DOWNLOAD_ZIP = "downloadZip";

压缩包的构建请看 service实现类的 downloadZip 方法。

文件下载的核心部分
举例子,实现文件下载,文件路径是网络地址(比如:OSS 云存储返回的地址)

Controller 部分:

@PostMapping(value = "/download")
public void downloadFile(HttpServletResponse response, String url) throws Exception {
    fileService.downloadFile(response, url);
}

Service 实现类部分,Service 接口类很简单(省略)

private static final String CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";

private static final String CONTENT_DISPOSITION = "Content-Disposition";

private static final String ACCESS_CONTROL_EXPOSE = "Access-Control-Expose-Headers";

private static final String FILE_NAME = "自定义文件名.xlsx";

public void downloadFile(HttpServletResponse response, String ossUrl) throws IOException {
    URL url = new URL(ossUrl);
    URLConnection conn = url.openConnection();
    conn.setConnectTimeout(3 * 1000);   // 设置超时间为3秒
    conn.setRequestProperty("User-Agent","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36");
    InputStream inputStream = conn.getInputStream();

    response.setContentType(CONTENT_TYPE);
    response.setHeader(ACCESS_CONTROL_EXPOSE, CONTENT_DISPOSITION);
    response.setHeader(CONTENT_DISPOSITION, "attachment; filename=" + URLEncoder.encode(FILE_NAME, CHARACTER));

    // 循环取出流中的数据
    byte[] b = new byte[1024 * 5];
    int len;
    while ((len = inputStream.read(b)) > 0) {
        response.getOutputStream().write(b, 0, len);
    }
    inputStream.close();
}

测试

EasyExcel3.0.5 导出多个sheet,批量下载打包成ZIP压缩包_第2张图片
EasyExcel3.0.5 导出多个sheet,批量下载打包成ZIP压缩包_第3张图片

结语

完整的项目代码结果,请移步查看 EasyExcel3.0.5导出多个sheet,含查询优化,内容太多,细节的代码不贴了,先把项目搭起来。 把上一篇的附件下载的实现替换成本篇的实现,就可以了。 代码发布到服务器依然可以下载到自己本地,同时多种多样的附件路径都可以下载解析。 好了,欢迎仁人志士留言评论。

你可能感兴趣的:(easyexcel,excel,spring,boot)