news 2026/6/23 20:01:50

Excel 批量导入实战:当 EasyExcel 遇上单元格嵌入附件

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Excel 批量导入实战:当 EasyExcel 遇上单元格嵌入附件

Excel 批量导入实战:当 EasyExcel 遇上单元格嵌入附件

适用场景:后端需要解析用户上传的 Excel,其中不仅包含常规文本数据,还包含直接嵌入单元格的附件(zip、docx、图片等),要求一次性完成数据入库、附件落盘、关联业务实体。


一、场景与痛点

在常见的 B 端后台中,运营/产品同学往往通过一份 Excel 模板批量录入业务数据。模板里除了常规字段,还经常出现“需求附件”列——用户把文件直接拖进 Excel 单元格里。这对后端提出了两个核心挑战:

  1. MultipartFile 的流只能读一次:EasyExcel 读完之后,如果再用 POI 读,流已经耗尽。
  2. Excel 里的附件不是超链接,而是二进制对象:需要精确解析 OLE、OOXML、图片三种嵌入形态,并按行号对应回业务数据。
  3. Excel中的附件类型,名称获取问题:excel对实体附件的数据和名称存储在不同的位置,需要根据特定的格式进行解析,且部分格式如:.xlsx和.docx格式的名称并无存储,本文暂时只找到对.zip的名称支持。

本文以“设计需求批量导入”模块为实例,给出一条可复用的工程化路径


二、技术栈选型

组件用途版本参考
EasyExcel快速读取文本数据、类型转换、注解映射2.x / 3.x
Apache POI (XSSF)解析底层 OOXML 结构,提取单元格嵌入对象5.x
Hutool字符串、集合、空值工具5.x
Spring Boot事务控制、Service 层编排3.x
自定义 OSS 工具附件上传、路径管理

为什么不只用 EasyExcel?
EasyExcel 专注于高性能的文本/数值读取,对单元格内嵌 OLE/图片对象只提供有限支持。对于“需要把附件捞出来并上传 OSS”的场景,必须下沉到 Apache POI 的XSSFWorkbook+XSSFDrawing层做原生解析。


三、整体架构流程

用户上传 Excel (.xlsx) │ ▼ ┌───────────────────┐ │ 1. 读取字节数组 │ ← 关键:先复制 bytes,解决流不可重复读 │ byte[] fileBytes │ └───────────────────┘ │ ┌───┴───┐ ▼ ▼ EasyExcel Apache POI (文本数据) (嵌入附件) │ │ ▼ ▼ List<VO> List<EmbeddingDesc> │ │ └───┬───┘ ▼ ┌───────────────────┐ │ 2. 按行号关联 │ ← 把附件挂到对应 VO 上 │ vo.setAttachments()│ └───────────────────┘ │ ▼ ┌───────────────────┐ │ 3. 逐行校验 │ ← 字典、用户、日期、多选字段、岗位-类型联动 │ validate/convert │ └───────────────────┘ │ ▼ ┌───────────────────┐ │ 4. 分批收集 │ ← 主表PO、用户绑定、Code映射、附件信息 │ 4 个临时集合 │ └───────────────────┘ │ ▼ ┌───────────────────┐ │ 5. 批量写入 │ ← 先插主表,再插关联表,最后上传附件 │ insert + OSS upload│ └───────────────────┘ │ ▼ ┌───────────────────┐ │ 6. 后置业务 │ ← 操作历史、审批流、待办、消息通知 └───────────────────┘

四、关键实现拆解

4.1 文件字节复制:解决流不可重复读

MultipartFile.getInputStream()只能消费一次。EasyExcel 和 POI 各自需要独立流,因此第一步就是把文件读成字节数组

byte[]fileBytes=file.getBytes();// 1. EasyExcel 读取List<DesignDemandImportVo>importVos=EasyExcel.read(newByteArrayInputStream(fileBytes)).head(DesignDemandImportVo.class).sheet("DesignDemand").doReadSync();// 2. POI 解析附件(同样基于 fileBytes)List<ExcelAttachmentParser.EmbeddingDesc>embeddings=ExcelAttachmentParser.resolve(fileBytes);

通用经验:凡是要用两套工具解析同一个上传文件,先读 bytes,再各自 new ByteArrayInputStream,不要直接传file.getInputStream()


4.2 双管道解析:文本 + 附件

文本管道:EasyExcel
@DatapublicclassDesignDemandImportVo{@ExcelProperty("品类")privateStringcategoryType_dictText;@ExcelProperty("希望交付时间")privateStringhopeDeliveryDate;@ExcelProperty("需求附件")privateStringattachmentZip;// 纯占位,不需要填内容@ExcelIgnoreprivateList<CellAttachment>attachments=newArrayList<>();// 真正存附件数据}
  • @ExcelProperty做列名映射;@ExcelIgnore字段不映射列,由代码手动填充。
  • 日期在 Excel 中可能是文本格式,所以先按String接收,再手动解析为LocalDate,避免格式错乱。
附件管道:Apache POI 原生解析

ExcelAttachmentParser.resolve(byte[] xlsxBytes)是核心工具类。它遍历XSSFDrawing,识别三种嵌入形态:

  1. OLE 对象:zip、docx、xlsx 等,通常以\u0001Ole10Nativepackage条目存储在POIFSFileSystem中。
  2. OOXML 原生嵌入:无 OLE2 包装的直接嵌入,直接读PackagePart的流。
  3. 粘贴图片XSSFPicture,通过XSSFPictureData获取 PNG/JPEG 字节。
publicstaticList<EmbeddingDesc>resolve(byte[]xlsxBytes){try(XSSFWorkbookworkbook=newXSSFWorkbook(newByteArrayInputStream(xlsxBytes))){for(inti=0;i<workbook.getNumberOfSheets();i++){XSSFSheetsheet=workbook.getSheetAt(i);XSSFDrawingdrawing=sheet.getDrawingPatriarch();if(drawing==null)continue;for(XSSFShapeshape:drawing.getShapes()){XSSFClientAnchoranchor=(XSSFClientAnchor)shape.getAnchor();introw=anchor.getRow1();// 获取附件所在行号if(shapeinstanceofXSSFObjectData){// OLE / OOXML 嵌入对象...}elseif(shapeinstanceofXSSFPicture){// 图片...}}}}}

OLE 文件名乱码是重灾区。Windows 中文系统默认用 GBK 编码文件名,但 Office 2016+ 可能用 UTF-8。解析器做了多编码尝试 + 启发式乱码检测

  • 优先 GBK → GB18030 → UTF-8 → 兜底 ISO-8859-1
  • 通过looksLikeMojibake()检测典型乱码特征(Âÿ等),反向选择正确编码。

下面是ExcelAttachmentParser完整代码

importlombok.extern.slf4j.Slf4j;importorg.apache.commons.io.IOUtils;importorg.apache.poi.openxml4j.opc.PackagePart;importorg.apache.poi.poifs.filesystem.*;importorg.apache.poi.xssf.usermodel.*;importjava.io.ByteArrayInputStream;importjava.io.IOException;importjava.io.InputStream;importjava.nio.ByteBuffer;importjava.nio.ByteOrder;importjava.nio.charset.StandardCharsets;importjava.util.*;/** * Excel单元格嵌入附件解析器 */@Slf4jpublicclassExcelAttachmentParser{publicstaticList<EmbeddingDesc>resolve(byte[]xlsxBytes){List<EmbeddingDesc>result=newArrayList<>();try(XSSFWorkbookworkbook=newXSSFWorkbook(newByteArrayInputStream(xlsxBytes))){// 获取所有嵌入式文件for(inti=0;i<workbook.getNumberOfSheets();i++){XSSFSheetsheet=workbook.getSheetAt(i);XSSFDrawingdrawing=sheet.getDrawingPatriarch();if(drawing==null)continue
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/23 19:57:28

基于XC7A100T-1FGG484I的高性能信号处理与数据采集系统设计

XC7A100T-1FGG484I&#xff1a;AMD Artix-7系列高性能低功耗FPGA深度解析在通信基础设施、工业自动化、医疗成像以及各类对性能和功耗有综合要求的嵌入式应用中&#xff0c;FPGA的选型往往需要在逻辑容量、I/O带宽和功耗之间寻求最佳平衡。AMD&#xff08;原Xilinx&#xff09;…

作者头像 李华
网站建设 2026/6/23 19:47:33

别踩 2026年自定义词库转写的坑:我实操总结的新手实用经验

先说明白核心判断 很多内容创作者做自定义词库转写都踩过坑&#xff0c;要么加了词准确率反而下降&#xff0c;要么想要批量导词不支持&#xff0c;要么免费版根本没法用这个功能。我作为长期测试AI效率工具的运营博主&#xff0c;实操对比了五款主流工具&#xff0c;总结下来&…

作者头像 李华
网站建设 2026/6/23 19:31:21

2026金九银十Java八股文面试题汇总(附答案·全栈覆盖)

最近我分析了几百份 2026 最新的大中小厂面经&#xff0c;整理了 Java后端 面试中最最最常问的一些问题&#xff01;小伙伴们可以对照着这篇文章来进行自测&#xff0c;这是一种非常不错的学习和复习方式。并且&#xff0c;每一年我都会根据当年的面试情况对其进行补充和完善。…

作者头像 李华
网站建设 2026/6/23 19:31:07

2026腾讯地图多场景技术方案科学选型指南

一、数字智能时代的空间计算战略机遇 在数字化转型进入深水区的当下&#xff0c;空间智能已成为企业构建核心竞争力的关键要素。地图大数据作为空间智能化的重要支撑&#xff0c;正加速与AI融合&#xff0c;推动从静态数据查询向动态预测与智能决策跃迁。这一趋势背后折射出的是…

作者头像 李华
网站建设 2026/6/23 19:18:22

LeetCode 每日一题笔记 日期:2026.06.19 题目:1840. 最高建筑高度

LeetCode 每日一题笔记 0. 前言 日期&#xff1a;2026.06.19题目&#xff1a;1840. 最高建筑高度难度&#xff1a;困难标签&#xff1a;数组、排序、贪心 1. 题目理解 问题描述 共有编号 1~n 的一排建筑&#xff0c;约束规则&#xff1a; 1号建筑高度固定为 0&#xff1b;相邻建…

作者头像 李华