ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SpringBoot整合EasyExcel:高效Excel导入导出与避坑实战

SpringBoot整合EasyExcel:高效Excel导入导出与避坑实战 简介面向Java后端开发者的Spring Boot示例围绕电子表格数据批量写入数据库、以及从数据库查询数据后生成电子表格两个高频业务场景配套接口测试集合、建表脚本和验证用电子表格适合有Java基础、想快速落地的读者。资源包共22个文件压缩后约40KB以10个Java源码文件为主辅以Maven配置、环境配置、SQL脚本、接口测试集合和电子表格样例目录划分清晰操作步骤在说明文档中有详述。项目采用分层结构数据源配置、解析逻辑、导入导出接口彼此分开便于按需替换或复用。按文档指引配置数据库并启动后借助接口调用即可完成导入导出演示可与测试表格核对结果降低上手和排错成本。目前已有7582人学习下载作为轻量级参考工程调整字段和接口即可集成到真实业务。1. 这套组合拳到底解决什么问题从 Excel 到数据库再回去的完整闭环做 Java 后端的人迟早会撞上这么个需求业务方甩来一张几十万行的 Excel让你把数据洗干净存进数据库过几天又说要把某张表导回 Excel 发给客户。单次转换不难难的是整个过程里那些说不清道不明的坑——编码乱掉、时间格式变样、导入一半报错导致数据半截入库、导出大数据量时内存直接溢出。这个标题讲的正是用 SpringBoot 把这两件事串成一条完整链路的标准做法核心用 Apache POI 或阿里 EasyExcel 做文件解析配合 MyBatis 或 Spring Data JPA 做持久化再通过 HttpServletResponse 把数据以 Excel 流的形式吐给前端。适合正在做后台管理系统、数据迁移工具或报表模块的 Java 工程师也适合准备这类面试题的人拿来当实战素材。我见过太多项目在这个环节上翻车不是没选对库就是没处理好异常和事务边界。这篇把理论立住、把代码写透、把常见的坑挑明照着复现一次基本能覆盖日常 80% 以上的导入导出场景。2. 选型先选对POI 还是 EasyExcel以及 SpringBoot 整合的最小依赖2.1 两者本质区别一个吃内存一个吃流Apache POI 是 Java 操作 Excel 的老牌库支持 xls 和 xlsx功能最全能精确到单元格样式、合并区域、公式计算但它有个臭名昭著的毛病解析大文件时会把整个工作簿读进内存。一个 50MB 的 xlsxPOI 解析时内存轻松飙到 500MB 以上服务器稍微紧张点就直接 OOM。而阿里开源的 EasyExcel 底层虽然也依赖 POI 的 SAX 模式但它是流式解析一行一读内存占用能控制在几十 MB 区间。如果说 POI 是大而全的重型工具EasyExcel 就是为大数据量导入导出场景量身定做的轻骑兵。如果你的数据量长期在几万行以内POI 完全够用但凡是可能上十万行、几十万行的场景我一般直接上 EasyExcel省得上线后被人追着说系统崩了。还有一点EasyExcel 不需要你关心 xlsx 和 xls 的差异它统一按 xlsx 处理对于新项目来说这反而省心。2.2 最小依赖配置Maven 里加什么、版本怎么锁以一个标准的 SpringBoot 2.x 项目为例必要的依赖有 spring-boot-starter-web、mybatis-plus-boot-starter或 spring-boot-starter-data-jpa、mysql-connector-java再加一个 easyexcel 和 lombok。这里有一个非常容易被忽略的依赖冲突EasyExcel 3.x 依赖的 poi 版本是 3.17而 poi-ooxml 的某些功能在 4.x 以上才有如果你在 pom 里单独引入更高版本的 poi轻则安全扫描报漏洞重则方法找不到直接 NoSuchMethodError。我习惯的做法是让 easyexcel 自己管理 poi 版本不在 pom 中显式声明 poi 相关坐标除非项目里已有其他模块强制引入了 poi。下面是一份能直接跑的最小 pom 依赖段dependency groupIdcom.alibaba/groupId artifactIdeasyexcel/artifactId version3.3.4/version /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.5/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency需要说明的是MyBatis-Plus 这里选 3.5.5 是因为它内置了批量插入的优化直接调用 IService 的 saveBatch 方法就能走 JDBC 批处理省去手写 SQL 拼接的麻烦。如果你的项目用的是 Spring Data JPA思路完全一样只是把 Mapper 换成 Repository核心流程不受影响。2.3 和数据库访问层的配合批量插入为什么要用 saveBatch 而不是 for 循环新手最容易写的代码是解析一行 insert 一行遇到十几万行的 Excel 时这种行为会产生十几万次数据库连接往返速度慢得让人怀疑人生而且任何一个字段违规都会导致事务回滚全部数据极其痛苦。常见做法是先把数据读到一个 List 里攒够一个批次再一次性写入。MyBatis-Plus 的 saveBatch 默认按 1000 条一批执行内部用 rewriteBatchedStatements 开启真正的 JDBC 批处理插入 10 万条数据从原来的一分多钟缩短到十秒以内效果非常明显。这里有个注意点saveBatch 是逐条预编译再批量执行的如果你在实体类里配置了逻辑删除字段或者自动填充字段插件会额外处理但性能损耗仍在可接受范围。真正可能让你惊讶的是第一次调用 saveBatch 时因为需要生成批量 SQL耗时偏长这是正常现象不要让监控告警把运维吓得大半夜爬起来找你。3. 解析 Excel 并写入数据库表结构设计、监听器模式与事务边界3.1 实体类与表结构注解映射是第一步不管你用什么解析库第一步永远是定义实体类和数据表。实体类里的字段要和 Excel 列一一对应用 EasyExcel 的 ExcelProperty 注解指定列名或列索引用 MyBatis-Plus 的 TableName 映射到表。这里有个很实际的问题Excel 里列头是中文Java 实体字段是驼峰命名数据库列是下划线命名三层映射要在一开始就理清楚。一种常见做法是用中文列名做 ExcelProperty 的 value数据库列名用 TableField 显式指定避免 SpringBoot 的驼峰转下划线规则在某些自定义 SQL 里失效。示例实体类如下Data TableName(user_import) public class UserImport { TableId(type IdType.AUTO) private Long id; ExcelProperty(姓名) private String name; ExcelProperty(年龄) private Integer age; ExcelProperty(入职日期) private LocalDate hireDate; ExcelProperty(部门) private String department; TableField(exist false) private String errorMsg; }注意 TableField(exist false) 这个字段它不参与数据库的增删改查纯粹用来存放校验失败时的错误描述方便在导入结束后把问题行单独生成一个错误报告 Excel 反馈给业务方。这个字段在导错报告时非常好用等会儿会展开讲。提示Excel 里的日期如果格式五花八门例如 2024/1/1、2024-01-01、44000 这种数字统一在解析阶段转成 LocalDate不要存字符串否则后面做 SQL 查询日期范围时会非常难受。3.2 监听器写业务逻辑AnalysisEventListener 的正确打开方式EasyExcel 的流式解析依赖监听器回调。每解析一行数据就会调用 invoke 方法读取完整个文件再触发 doAfterAllAnalysed。业务代码最自然的写法是在 invoke 里做数据校验、转换、累加到一个 list每攒够 1000 条调用一次批量插入然后清空 list。这样既能控制内存又能保证写入效率。下面是一个完整的监听器实现把校验、转换、批量插入都收拢在一起public class UserImportListener extends AnalysisEventListenerUserImport { private static final int BATCH_COUNT 1000; private ListUserImport batchList new ArrayList(); private ListUserImport errorList new ArrayList(); private final UserImportService userImportService; public UserImportListener(UserImportService userImportService) { this.userImportService userImportService; } Override public void invoke(UserImport data, AnalysisContext context) { // 基础校验年龄必须大于0小于150姓名不能为空 if (data.getName() null || data.getName().trim().isEmpty()) { data.setErrorMsg(姓名为空); errorList.add(data); return; } if (data.getAge() ! null (data.getAge() 0 || data.getAge() 150)) { data.setErrorMsg(年龄不合法); errorList.add(data); return; } batchList.add(data); if (batchList.size() BATCH_COUNT) { saveBatch(); } } private void saveBatch() { if (!batchList.isEmpty()) { userImportService.saveBatch(batchList); batchList.clear(); } } Override public void doAfterAllAnalysed(AnalysisContext context) { saveBatch(); } public ListUserImport getErrorList() { return errorList; } }这段代码的逻辑链条很清晰invoke 每被调用一次就收到一行数据先做基础合法性校验不通过的行直接进 errorList不阻塞其余行的导入。通过校验的行攒到 1000 条就批量落库。doAfterAllAnalysed 保证文件结束前最后不足 1000 条的数据也能被写入。这里需要讲一下为什么校验失败的行不抛异常而是收集到 errorList。生产环境中最普遍的需求是好坏行隔离不能因为 2 行脏数据让整个导入任务失败那会让业务方骂街。收集错误行后可以在接口返回里告诉他们导入成功 9998 条、失败 2 条失败原因见附件。这才是能被实际项目接受的交互方式。3.3 Controller 层文件接收与异步化的平衡Controller 里接收 MultipartFile 后直接调用 EasyExcel.read 传入输入流和监听器即可。但这里有一个非常关键的设计决策如果 Excel 文件很大同步处理会让 HTTP 请求长时间挂起网关超时、前端超时都是麻烦事。常见做法是把解析入库这个动作丢给线程池异步执行接口立刻返回任务已提交前端轮询任务状态。下面给出同步写法的完整 Controller它适合中小数据量清晰易懂RestController RequestMapping(/import) public class UserImportController { private final UserImportService userImportService; public UserImportController(UserImportService userImportService) { this.userImportService userImportService; } PostMapping(/excel) public MapString, Object importExcel(RequestParam(file) MultipartFile file) throws IOException { UserImportListener listener new UserImportListener(userImportService); EasyExcel.read(file.getInputStream(), UserImport.class, listener) .sheet() .headRowNumber(1) .doRead(); MapString, Object result new HashMap(); result.put(successCount, listener.getErrorList().isEmpty() ? 全部成功 : 有失败); result.put(errorList, listener.getErrorList()); return result; } }代码里的 headRowNumber(1) 指定了标题行在第 1 行从第 2 行开始正式读取数据。如果你的 Excel 前两行是合并单元格的大标题这里就要改成 2。sh 这是一个非常常见的翻车点——不同模板的标题行位置不同换模板时忘改这个参数轻则数据错位重则把表头当数据存进库那是真灾难。假如你的项目从一开始就要面对超大文件把导入任务改异步的执行流程是Controller 收到文件后存入临时目录或云存储把文件路径和任务 ID 丢进线程池返回前端一个任务 ID线程池里的任务调用同一个监听器完成解析入库前端用任务 ID 轮询进度。这个方案能让导入接口永远秒回避免所有 HTTP 层超时问题。代价是多一张任务状态表需要维护但对生产级系统来说是值得的。3.4 事务边界回滚哪些、不回滚哪些、为什么导入场景的事务格外容易出岔子。有的项目为了保险把整个文件解析都包在 Transactional 里结果一个脏数据导致 10 万行全部回滚。这不是保险是定时炸弹。合理的边界是只有批量插入这一步需要事务解析和校验放在事务外。MyBatis-Plus 的 saveBatch 默认自带事务每条数据的插入都在同一个事务里执行批次内任一条失败则本批全部回滚但这个回滚不会影响之前已经成功写入的批次。这恰好符合好坏行隔离的预期批次之间的数据完全独立坏行所属的批次最多影响自身那一批其他批次正常落库。如果你希望整个文件要么全成要么全败可以把所有数据先解析到内存中的 list 里然后在 service 方法上加 Transactional 统一次性插入。但这么做只适合 1 万行以内的小文件超过这个量内存和事务时长都会成为新的问题。这两条路线没有绝对的对错取决于业务到底是容忍部分成功还是必须原子完成提前和业务方确认远比事后补救省心。4. 把数据库数据导出成 Excel模板、流式写出与自定义样式4.1 导出接口的标准姿势HttpServletResponse 与 MIME 类型导出数据和导入不同它是数据的单向输出核心诉求是文件不生僻、打开不乱码、大表不 OOM。SpringBoot 里导出 Excel 的标准姿势是查询数据库得到数据列表用 EasyExcel.write 拿到 OutputStream 写入响应流同时设置响应头让浏览器识别为附件下载。这一段代码是导出功能的最小骨架。要点是响应头的 Content-Disposition 必须用 URLEncoder 编码文件名否则浏览器下载时中文文件名会变成一堆百分号。编码格式建议用 UTF-8并且设置 Content-Type 为 application/vnd.openxmlformats-officedocument.spreadsheetml.sheet对应 xlsx 的 MIME 类型。GetMapping(/export) public void exportUser(HttpServletResponse response) throws IOException { String fileName URLEncoder.encode(用户数据_ System.currentTimeMillis(), UTF-8); response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setCharacterEncoding(utf-8); response.setHeader(Content-disposition, attachment;filename fileName .xlsx); ListUserImport userList userImportService.list(); EasyExcel.write(response.getOutputStream(), UserImport.class) .sheet(用户数据) .doWrite(userList); }这里有一个坑要特别提醒数据量大时不要一次性把所有数据查出来放内存再写而是用 MyBatis-Plus 的分页查询或者游标查询逐批取数、逐批写入。EasyExcel 的 doWrite 可以接收一个 List也可以配合 WriteHandler 实现分批写。实际开发里更常见的是自己控制查询批次每次查 1 万条就 write 一次用多次查询代替一次巨大查询。这能同时降低数据库查询压力和 JVM 内存压力。4.2 让 OpenCSV 风格的列映射滚蛋注解构建导出模板EasyExcel 的导出和导入共用一套实体类注解这省去了大量写列映射的功夫。导出时 ExcelProperty 的 value 直接变成 Excel 表头字段顺序决定列顺序。这里有个巧妙的用法如果导入和导出对同一批字段的表头要求不一样Excel 里常发生可以用两个不同的 DTO 类来分别承接导入和导出互不干扰。过度复用同一个实体类做导入导出遇到业务方说导入模板要叫工号导出的表头要叫员工编号时就得改代码。如果你需要在导出时指定列宽、行高、冻结首行、背景色可以自定义一个 CellWriteHandler再在 EasyExcel.write 之后调用 registerWriteHandler 注册它。下面示例展示的是把表头行背景色设为浅灰色、字体加粗、所有列宽自适应内容public class CustomCellWriteHandler implements CellWriteHandler { Override public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, ListWriteCellData? cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { if (isHead) { CellStyle cellStyle writeSheetHolder.getSheet().getWorkbook().createCellStyle(); cellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); cell.setCellStyle(cellStyle); } } }这段代码的触发时机是每个单元格写完内容之后isHead 参数为 true 表示当前是表头行。把它注册进 EasyExcel 的 writer 后表头会自动带斑马纹底色开起来更专业。但注意这个 handler 是写所有 sheet 共用的如果项目里多 sheet 导出且不同 sheet 要不同样式需要根据 WriteSheetHolder 的 sheet 名做分支判断。自定义样式这一小步最容易出彩也最容易出错尤其是单元格边框和背景色的设置需要手动 new 一个 CellStyle避免直接修改 workbook 默认样式对象否则会影响其他 sheet 的格式。4.3 大数据量导出分批查询加分批写的完整代码当数据量跨越百万行时一次性查询全部数据再写入既不可行也没必要。常见做法是每次从数据库取出 2 万行调用 EasyExcel 的 write 多次最后一次调用 finish 方法关闭流。下面这套代码是我多次用于生产环境的分批导出模板GetMapping(/export/big) public void exportBigUser(HttpServletResponse response) throws IOException { String fileName URLEncoder.encode(百万用户数据, UTF-8); response.setHeader(Content-disposition, attachment;filename fileName .xlsx); ExcelWriter excelWriter EasyExcel.write(response.getOutputStream(), UserImport.class).build(); WriteSheet writeSheet EasyExcel.writerSheet(用户数据).build(); long currentId 0L; int pageSize 20000; while (true) { LambdaQueryWrapperUserImport wrapper new LambdaQueryWrapper(); wrapper.gt(UserImport::getId, currentId).orderByAsc(UserImport::getId).last(LIMIT pageSize); ListUserImport pageData userImportService.list(wrapper); if (pageData.isEmpty()) { break; } excelWriter.write(pageData, writeSheet); currentId pageData.get(pageData.size() - 1).getId(); } excelWriter.finish(); }这段代码看起来简单背后有两条关键经验。第一用 ID 游标代替传统 OFFSET 分页深分页时 OFFSET 会让数据库做大量无用扫描而 gt currentId 完全走索引百万级数据越查越快这和很多面试题里聊的分页优化完全一致。第二excelWriter.write 会缓存到内存再写如果每批条数太小比如 1000 条会造成频繁刷盘写入速度降低如果调太大又可能内存抖动。我实测下来 2 万条一批是性能和内存的平衡点你可以根据自己的服务器配置微调。还有个容易踩的坑是最后一批数据不满 2 万时会自然跳出循环但如果最后一页正好空数据循环也不会多跑一次。真正需要注意的反而是 finish 方法体会被调用——这是一个非常常见的问题。如果忘记调用 finish生成的 Excel 文件会损坏打开时会提升缺少结尾标记。即便写得很好的导出代码在这种细节上丢掉文件完整性也是极其丢人的事情。5. 导入导出避坑指南五条最容易翻车的血的教训5.1 日期的时区与格式陷阱2007 版 Excel 天数序列值现象Excel 里明明写的是 2024-06-15导入数据库后变成了 1989-03-22 或者 00:00:00 消失只剩日期。原因有两层一是 EasyExcel 在将 EXCEL 日期转为 Java 对象时依赖 POI 对 Excel 日期特殊编码的解析旧版 poi 不识别某些欧洲区域格式会把它当纯数字处理二是数据库驱动连接串里没设置 serverTimezone导致 LocalDate 被 JVM 默认时区转成了另一个日期。解决方法是把 Excel 日期列统一转成字符串再用 DateTimeFormatter 解析。更保险的连接串写法是 jdbc:mysql://localhost:3306/db?serverTimezoneAsia/Shanghai。如果源文件来自不同时区的业务方你还需要在代码里指定 ZoneId避免夏令时造成的偏移坑。5.2 数字精度丢失Excel 里的大整数变了样现象业务方在 Excel 里填的订单号是 6227001234567890123 这种 19 位数字导入数据库后变成 6227001234567890000后几位全部归零。原因很符合 Java 处理浮点时的精度丢失机制POI 读数字默认按 Double 处理而 double 只能精确表示大约 2 的 53 次方以内的整数。解决方法是把 Excel 这一列在模板里设置成文本格式Java 实体字段对应写成 String解析时优先读取字符串而非数字。如果你没法控制上游模板就在监听器的 invoke 里先拿 CellData 判断类型再做转换而不是直接 getNumber 然后强转 long。5.3 大数据量写库时的连接耗尽现象导入 50 万行的文件跑到一半报错 Cannot get a connection, pool timeout。原因是每攒够 1000 条就 saveBatch 一次如果数据库连接池最大连接数是 20而批量插入速度太慢连接被占满就抛超时。另一个隐性原因是解析线程和请求线程各自占用连接但没有血缘关系。解决方法是调大连接池的 maximum-pool-size同时把批量插入的批次加大到 3000 或 5000 再落库降低数据库连接的使用频率。更稳妥的做法是让解析入库线程池的信号量控制并发连接池按并发量配置。如果你用的是 HikariCP调 maximumPoolSize 的代价很小别怕改。5.4 内存溢出xlsx 的行数远超预期现象一个小伙伴自信地用 POI 的 XSSFWorkbook 去解析一个 40MB 的 xlsx结果堆内存设了 512MB直接 OOM。原因就是之前聊过的 POI 会把整个 Sheet 读进内存构建 DOM 树。解决方法是先检查文件的扩展名和实际内容用 EasyExcel 的流式解析代替 XSSFWorkbook。如果项目里确实必须用 POI请用 XSSFWorkbook 只读模式的 SAX 解析或者升级到 XSSFReader这种 API 专门为大数据量设计。这里有个识别技巧同一份 Excel用 EasyExcel 和 POI 各跑一次观察内存曲线EasyExcel 的峰值通常只有 POI 的五分之一到十分之一这个差距在中低配服务器上就是卡死和流畅的分界线。5.5 导出文件打开就报文件已损坏现象导出成功后点击文件Excel 提示文件格式或扩展名无效。原因最常见的是 response 流被提前关闭或者写了部分内容但没调用 finish 方法。另一种情况是导出查询出的数据里含有 Java 无法持久化的字段比如代理懒加载对象的反序列化异常被写入了文件。解决方法是确认导出逻辑中的所有分支都会在 finally 块里调用 excelWriter.finish()同时检查 DTO 字段类型必须全部是 Excel 支持的简单类型遇到一对一关联对象时先 map 成扁平字段再写。这个坑比较隐蔽的地方在于它不一定每次都失败文件小、数据少时可能侥幸逃过数据一旦多了必然会出问题。6. 进阶玩法多 Sheet 拆分、失败报告生成与任务异步化做到这个程度导入导出已经能解决大部分业务的日常需求但生产环境总有更苛刻的场景。我经历过的一个典型需求是业务方提供 5 个不同 sheet 的 Excel每个 sheet 对应不同的表结构需要一次性全部入库且某个 sheet 失败不能影响其他 sheet。EasyExcel 的 doReadAll 可以读多个 sheet每个监听器各管一个 sheet但这里有个不可忽视的细节doReadAll 是串行解析sheet 数量多时总耗时是累加的而且不同 sheet 的实体类不同需要单独设置监听器。另一种途方是用 excelReader 同时注册多个 readSheet在同一个文件句柄上轮流解析内存占用更稳定。实现方式是这样ExcelReader excelReader EasyExcel.read(file.getInputStream()).build(); ReadSheet sheet1 EasyExcel.readSheet(0).head(UserImport.class).registerReadListener(userListener).build(); ReadSheet sheet2 EasyExcel.readSheet(1).head(DepartmentImport.class).registerReadListener(deptListener).build(); excelReader.read(sheet1, sheet2); excelReader.finish();代码本身很短但这个写法解决了一个很重要的问题多 sheet 文件导出时解析器不再是一次只认一张表的结构。对于配置了 headRowNumber 的模板来说每个 sheet 单独设置监听器的做法也避免了表头解析互相污染的问题。多 sheet 场景常常伴随另一种需求导入结束后给业务方一份错误报告把校验失败的行原样输出然后追加一列错误原因。实现方法是用之前实体类里的 TableField(exist false) 的 errorMsg 字段把错误行收集成一个 List再调用 EasyExcel.write 生成一个只含错误行的 Excel 文件。下面这段代码可以放到导入结束后调用private void writeErrorReport(ListUserImport errorList, HttpServletResponse response) throws IOException { String fileName URLEncoder.encode(导入错误报告, UTF-8); response.setHeader(Content-disposition, attachment;filename fileName .xlsx); EasyExcel.write(response.getOutputStream(), UserImport.class) .sheet(错误数据) .doWrite(errorList); }注意这里直接复用 UserImport 作为导出表头Excel 里会看到最后一列是 errorMsg业务方一下子就懂哪行出了什么问题。如果你想让错误报告更友好可以单独定义一个 ErrorReportDTO只暴露 Excel 原有列加错误原因列不带数据库 ID 等内部字段。这是一个典型的需求简单、细节多的活做得好能显著减少和业务方的来回沟通时间。异步化是另一个值得投入的方向。现在的导入接口如果是同步的在面对几十万行数据时长时间的 HTTP 连接断线风险会变得非常高而且用户只能干等页面转圈。我的习惯是做一个导入任务表提交时就是插入一个任务记录返回任务 ID。后台线程池立即消费这个待处理任务解析完 Excel、写入数据库、更新任务状态为完成。前端用定时器轮询任务状态拿到完成再把错误报告下载链接展示给用户。这个改造的技术细节不算复杂但能直接改变使用体验也让系统在处理超大文件时不再受制于网关超时。如果项目已经有消息队列可以把解析任务丢进 MQ 做削峰但没 MQ 的时候用线程池加任务表也足够稳。最后聊一个我自己的习惯每次做完导入导出功能我会留下一个特定构造的测试 Excel里面有乱码列名、空行、日期格式混排、数字超精度这四类脏数据专门用来回归测试。上线前跑一遍能拦住大量低级的解析事故。导入导出这段链路看着简单实际藏了太多边界情况测试用例里的那点时间永远比上了生产再回滚的代价小得多。希望这些经验对你手头的 SpringBoot 导入导出模块有点实际帮助。本文还有配套的精品资源点击获取
返回列表