SpringBoot中使用POI+EasyExcel批量导出主子表信息,以箱单为例

如果填充模板采用写死的方式,比如第2行第3列填充什么内容,直接用POI就可以实现,不需要EasyExcel,也比较简单。

如果只导出一个单层机的信息也比较简单。

今天讲的主要是要求用替换占位符的方式填充Excel,并且数据是主子表信息,主表单条子表多条的这种数据。

  1. 首先配置模板,如下:

    其中{contract_number}{box_number}为表头信息,而下面的{list.goods_code}等则为表体信息。

  2. 将该模板文件命名为BoxList.xlsx,并放到resources/excel-template下。

  3. 引入EasyExcel依赖:

xml 复制代码
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>easyexcel</artifactId>
	<version>4.0.3</version>
</dependency>
  1. 生成文件代码如下:
java 复制代码
Map<String, Object> boxMap = new HashMap<>();
boxMap1.put("contract_number", "HT-2026-001");
boxMap1.put("box_wrap_number", "BOX-20260423-001");

List<Map<String, Object>> detail = new ArrayList<>();
Map<String, Object> d1_1 = new HashMap<>();
d1_1.put("goods_code", "GC260424-1524");
d1_1.put......;

detail.add(d1_1);
boxMap.put("list", detail);

// 读取Excel模板
try (InputStream template = new ClassPathResource("excel-template/BoxList.xlsx").getInputStream()) {
                {
                    // excelFilePath为保存文件的路径,需要进行预创建
                    String fileName = excelFilePath + File.separator + "包装箱单_" + boxMap.get("id") + ".xlsx";
                    // 准备写入文件,声明即将写入的文件和模板
                    ExcelWriter excelWriter = EasyExcel.write(fileName)
                            .withTemplate(template)
                            .build();
                    WriteSheet writeSheet = EasyExcel.writerSheet().build();
                    // 写入表头信息
                    excelWriter.fill(boxMap, writeSheet);
                    List<Map<String, Object>> detailList = (List<Map<String, Object>>) boxMap.get("list");
                    // 写入表体的配置
                    FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
                    FillWrapper wrapper = new FillWrapper("list", detailList);
                    // 写入表体信息
                    excelWriter.fill(wrapper, fillConfig, writeSheet);
                    // 正式写入文件
                    excelWriter.finish();
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }

这样就可以成功写入文件了。注意在EasyExcel.write()......中,不要加.autoTrim()以及其他多余的东西,否则可能会写入失败。

如果需要同时生成多个箱单,并通过zip压缩方式导出,往下看。

  1. 生成多个箱单Excel,并通过zip压缩方式导出,直接上接口代码:
java 复制代码
@GetMapping(value = "/exportBoxList")
public void exportBoxList(@RequestParam(name = "ids") String ids, HttpServletResponse response) {
	List<Map<String, Object>> boxMapList = boxService.getBoxList(ids);
	List<File> fileList = new ArrayList<>(boxMapList.size());
	for (Map<String, Object> boxMap : boxMapList) {
		try (InputStream template = new ClassPathResource("excel-template/BoxList.xlsx").getInputStream()) {
			{
                                // 这里注意,箱号如果包含特殊符号,这里就不要用箱单做名字了,否则可能导致路径混乱找不到文件
				String fileName = excelFilePath + File.separator + "箱单_" + boxMap.get("box_number") + ".xlsx";
				ExcelWriter excelWriter = EasyExcel.write(fileName)
						.withTemplate(template)
						.build();
				WriteSheet writeSheet = EasyExcel.writerSheet().build();
				excelWriter.fill(boxMap, writeSheet);
				List<Map<String, Object>> detailList = (List<Map<String, Object>>) boxMap.get("list");
				FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
				FillWrapper wrapper = new FillWrapper("list", detailList);
				excelWriter.fill(wrapper, fillConfig, writeSheet);
				excelWriter.finish();
				fileList.add(new File(fileName));
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	try {
		response.setContentType("application/octet-stream");
		response.setHeader("Content-disposition","attachment;filename=" + URLEncoder.encode("箱单.zip", "UTF-8"));
		OutputStream os = response.getOutputStream();
		ZipUtils.downloadZipForFiles(os, fileList);
		os.flush();
		os.close();
		for (File file : fileList) {
			file.deleteOnExit();
		}
	} catch (IOException e) {
		throw new RuntimeException(e);
	}
}

就到这里。后面附上ZipUtils的代码:

java 复制代码
package org.jeecg.common.util;

import java.io.*;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import static org.springframework.util.StreamUtils.BUFFER_SIZE;
 
/**
 * @Author: wyf
 * @Date: 2026-4-23 16:02
 * @Description: zip工具类
 */
public class ZipUtils {
    /**
     * 传入文件file
     * @param outputStream
     * @param fileList
     */
    public static void downloadZipForFiles(OutputStream outputStream, List<File> fileList){
        ZipOutputStream zipOutputStream = null;
        try {
            zipOutputStream = new ZipOutputStream(outputStream);
            for (File file : fileList) {
                ZipEntry zipEntry = new ZipEntry(file.getName());
                zipOutputStream.putNextEntry(zipEntry);
                byte[] buf = new byte[BUFFER_SIZE];
                int len;
                FileInputStream in = new FileInputStream(file);
                while ((len = in.read(buf)) != -1) {
                    zipOutputStream.write(buf, 0, len);
                    zipOutputStream.flush();
                }
            }
            zipOutputStream.flush();
            zipOutputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭流
            try {
                if (zipOutputStream != null ) {
                    zipOutputStream.close();
                }
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
 
    /**
     * 传入文件的 byte[]
     * Map<String,byte[]> fileBufMap key是文件名(包含后缀),value是文件的byte[]
     * @param outputStream
     * @param fileBufMap
     */
    public static void downloadZipForByteMore(OutputStream outputStream,Map<String,byte[]> fileBufMap)  {
            ZipOutputStream zipOutputStream = null;
            try {
                zipOutputStream = new ZipOutputStream(outputStream);
                for (String fileName:fileBufMap.keySet()){
                    ZipEntry zipEntry = new ZipEntry(fileName);
                    zipOutputStream.putNextEntry(zipEntry);
                    if (Objects.nonNull(fileBufMap.get(fileName))){
                        byte[] fileBytes = fileBufMap.get(fileName);
                        zipOutputStream.write(fileBytes);
                        zipOutputStream.flush();
                    }
                }
                zipOutputStream.flush();
                zipOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                // 关闭流
                try {
                    if (zipOutputStream != null ) {
                        zipOutputStream.close();
                    }
                    if (outputStream != null) {
                        outputStream.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    }
 
    /**
     * 返回zip包的 byte[]
     *
     * @param fileBufMap
     * @return
     */
    public static byte[] getZipForByteMore(Map<String,byte[]> fileBufMap)  {
        ByteArrayOutputStream totalZipBytes = null;
        ZipOutputStream zipOutputStream = null;
        try {
            totalZipBytes = new ByteArrayOutputStream();
            zipOutputStream = new ZipOutputStream(totalZipBytes);
            for (String fileName:fileBufMap.keySet()){
                ZipEntry zipEntry = new ZipEntry(fileName);
                zipOutputStream.putNextEntry(zipEntry);
                if (Objects.nonNull(fileBufMap.get(fileName))){
                    byte[] fileBytes = fileBufMap.get(fileName);
                    zipOutputStream.write(fileBytes);
                    zipOutputStream.flush();
                }
            }
            zipOutputStream.close();
            byte[] bytes = totalZipBytes.toByteArray();
            totalZipBytes.close();
            return bytes;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (totalZipBytes != null) {
                    totalZipBytes.close();
                }
                if (zipOutputStream != null) {
                    zipOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}
相关推荐
fengxin_rou3 分钟前
MySQL 索引高频面试题全解析:B + 树、联合索引、索引失效
后端·mysql
牙牙要健康6 分钟前
Windows 下为 VSCode 配置 MSVC 编译工具链:从零安装 Build Tools 到完整配置教程
ide·windows·vscode
橘子海全栈攻城狮20 分钟前
【最新源码】基于springboot的快递物流平台的设计与实现C102
java·开发语言·spring boot·后端·spring·web安全
梦梦代码精23 分钟前
开源智能体平台 BuildingAI 深度解析:Monorepo 架构、MCP 集成及 GPT-Image-2 接入实测
前端·人工智能·后端·gpt·开源·github
fanzhonghong27 分钟前
javaWeb开发之前端实战(Tlias案例-部门管理)
前端·后端·web·前后端分离
海盗123431 分钟前
C#中使用MiniExcel 快速入门:读写 .xlsx 文件
开发语言·windows·c#
Ting-yu31 分钟前
SpringCloud快速入门(11)---- Sentinel(异常处理)
java·spring boot·后端·spring·spring cloud·sentinel
_童年的回忆_32 分钟前
【Linux】安装Jenkins并且打包发布springboot项目
linux·spring boot·jenkins
邪修king35 分钟前
UE5 TA 核心修炼:材质与纹理艺术全解 —— 从 PBR 理论到工业级材质实战
c++·后端·游戏·ue5·材质
彭于晏Yan40 分钟前
Maven 资源插件:非过滤文件后缀配置及风险规避
java·spring boot·maven