乐于分享
好东西不私藏

10秒解决 Excel 导入遇到的诡异问题

10秒解决 Excel 导入遇到的诡异问题

“没有做过附件处理的程序员不算一个合格的滴滴司机,附件中的隐形坑90%开发者不知道”

01

—

背景

话说,在日常的开发中,Excel 批量导入ID、编码、门店号是非常高频的操作。最近业务同学在使用一个后台系统的导入功能时系统报了一个错误提示:

是不是平时群里回复太积极了,上来就圈我,又不是我做的。根据提示找代码看了下:

逻辑还是比较简单:Excle解析出来的数据如果不满足业务规则,错误变量加1,当不等于0的时候说明某列不合法就会提示,于是先反弹回去:为啥子每天这么多人用,别人没反应,就你有,让对方检查数据格式,我悄悄的研究了一把。

02

—

拉扯

其实做开发的大都是心软的人,特别是女孩子反应的问题,在好奇心的加持下研究了一把,技术栈也很标准:

  • SpringBoot
  • Apache POI(org.apache.poi.ss.usermodel.Workbook)
<dependency>    <groupId>org.apache.poi</groupId>    <artifactId>poi-ooxml</artifactId>    <version>3.15</version></dependency><dependency>    <groupId>com.amazonaws</groupId>    <artifactId>aws-java-sdk-s3</artifactId>    <version>1.9.0</version></dependency>
  • 读取 Excel (模拟一些数据)→ 转为 String → 校验入库
规则很明确:门店 ID 固定为 8 位纯数字、后端使用正则: ^\d+$ 做严格格式校验、只允许 0-9,不允许出现任何字母、小数点、符号,一切看起来都很稳妥,但确实报错了。既然肉眼完全看不出任何问题,只能上强度,把附件中的拷出来到IDE跑一下就知道了,当我贴到IDE的时候多了个\n,我又放到文本编辑器中,发现后面都多了一个,到这里我初步怀疑跟这个有关

正当我准备斩钉截铁回复时,对方说用AI分析了下,果然不出我所料,AI说没有任何问题,是纯数字:

就她会AI?我的豆包呢?结果叛变了,这边文章的基因就来自这里

到这里有的同学是不是跟豆包不谋而合,怀疑是附件数据格式的问题,如果早就有这个想法的,我得竖大拇指——替你们试过了,不行的

03

—

复现

上面都是猜测,好在代码可以跑起来,实测了一下发现其中一个变了:

惊不惊喜,意不意外。是不是想知道为啥?观察发现:末尾带 0 的数字,被转成科学计数法,难道是巧合?在出问题的数字前后我又随便造几个假数据,重启后确认末尾0就有这个问题。
如果是数字有这个问题,前面设置单元格格式为文本为什么无效,况且定义为:String shopId,为啥呢

04

—

WHY

以前查百度,现在问AI方便多了(百度要加油啊),结论如下:

这是 Excel 和 Java 数值解析的共同特性,核心原因是有效数字位数阈值:

  • Excel 的「常规」格式,对于有效数字超过 6 位的整数,会自动用科学计数法显示 / 存储;
  • 末尾带 0 的 8 位数字,刚好卡在这个阈值上:
比如出问题的32051060:8 位数字,末尾 1 个 0,有效数字是 7 位,超过 6 位,Excel 会自动转为科学计数法3.205106E+7,Java 框架读取后,转成 String 就会保留这个科学计数法格式;
比如正常的32051061:8 位数字,末尾无 0,有效数字是 8 位,Excel「常规」格式下会完整显示,Java 框架读取后也能拿到完整的数字字符串。
至于为什么没有按定义赋值:对于数值类型的单元格,框架会先把内容解析成Double数值类型,再转成String字符串;哪怕你字段定义的是
String,也拦不住这个「先转数值,再转字符串」的过程,科学计数法就是在这个环节被带进来的。查询源码发现果然如此:
下一个问题就是如何修复呢?最简单的方法是不是升级maven就可以了,结果人家是明牌

还是好奇,既然前面提到Excel超过6位才有这个问题,我又造了几个数字试了下:

Case 1:3205900(7 位数字,末尾 2 个 0)

  • 数值大小:3205900,7 位整数,有效数字 7 位,超过 6 位临界值
  • 读取结果:3.2059E6(科学计数法)
  • 正则校验结果:❌ 失败(包含小数点、字母 E)
  • 为什么会触发?7 位数字,有效数字超过 6 位,且末尾带 0,是 10 的整数次幂的倍数

Case 2:32051060(8 位数字,末尾 1 个 0)

  • 数值大小:32051060,8 位整数,有效数字 7 位,超过 6 位临界值
  • 读取结果:3.205106E7(科学计数法)
  • 正则校验结果:❌ 失败
  • 为什么会触发?同上

Case 3:32054928(8 位数字,末尾无 0)

  • 数值大小:32054928,8 位整数,有效数字 8 位,超过 6 位临界值
  • 读取结果:32054928(完整纯数字字符串)
  • 正则校验结果:✅ 完全通过
  • 为什么不会触发?虽然有效数字超过 6 位,但末尾不带 0,不是 10 的整数次幂的倍数

Case 4:123456(6 位数字,末尾无 0)、123000(6 位数字,末尾 3 个 0)

  • 读取结果:均为完整的纯数字字符串,无科学计数法
  • 正则校验结果:✅ 全部通过
  • 原因:6 位及以内数字,有效数字未超过 6 位的临界阈值,Excel 和 Java 都不会触发科学计数法的优化,天然安全。

05

—

解决方案

既然升级maven不能解决,那么就是编码,Apache POI 官方给了对应的解决方案——贴DataFormatter于患处,10秒内方可驱散不干净的东西,症状缓解,如下所示:
import org.apache.poi.ss.usermodel.DataFormatter;import org.apache.poi.ss.usermodel.Cell;// 全局单例private static final DataFormatter DATA_FORMATTER = new DataFormatter();// 正确读取方式String cellValue = DATA_FORMATTER.formatCellValue(cell).trim();
最后,有没有人好奇,科学计数法也是一个数字啊,为什么正则:\d+$会校验失败?
在数学数值上是完全合法的数字,它等于32051060,数值上没有任何问题。但它的字符串形式里,包含了 3 个正则不允许的字符:小数点./字母E/加号+,「数值意义上的数字」和「字符串形式的纯数字」是两个完全不同的概念。
总体来说:这不是 POI的Bug,而是 Excel+POI+Java 三者标准行为叠加的连锁反应。如果有人看到这里,有谁敢留言证明这个问题对阁下来说是已知的
传送门:
  • Apache POI 官方 DataFormatter说明:

https://poi.apache.org/apidocs/dev/org/apache/poi/ss/usermodel/DataFormatter.html

  • Java 官方 Double.toString () 科学计数法触发规则:

https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Double.html#toString(double)

日拱一卒,功不唐捐。
——《法句经》

相关学习资料