最近浏览语雀社区官网,里面有一篇 easyexcel 里面 图片导出,对此,我产生了浓厚兴趣。恰巧公司有这么一个BT 需求,但是作为有追求的人,图片放在 excel 单元格是不是太不美观了,万一图片很大,那不是奇丑无比了! 有没有什么办法让图片下载到指定的本地文件路径,这样excel 既美观,又不影响图片查看。
上一次写了一篇: EasyExcel3.0.5导出多个sheet,含查询优化,竟然无人问津,这篇写的非常完整,既实现了多个 sheet 导出,也实现了附件下载。但是批量下载附件有几个问题点
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
好了,遇到问题解决问题,才能帮助自己成长。话不多说,开干!
为了让读者可以轻松读懂读通,并可以参照实现,还是贴完整一点。
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();
}
}
声明:
代码核心:
分析过程只截取部分重点。
多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,含查询优化,内容太多,细节的代码不贴了,先把项目搭起来。 把上一篇的附件下载的实现替换成本篇的实现,就可以了。 代码发布到服务器依然可以下载到自己本地,同时多种多样的附件路径都可以下载解析。 好了,欢迎仁人志士留言评论。