news 2026/4/18 7:12:45

含源码可复用:Spring Boot 实现 Excel 导入导出与模板下载一站式方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
含源码可复用:Spring Boot 实现 Excel 导入导出与模板下载一站式方案

一 功能总览与关键点

  • 模板下载:从classpath读取固定模板文件,通过HttpServletResponse输出为附件,设置正确的Content-TypeContent-Disposition,兼容中文文件名。
  • 批量导入:接收MultipartFile,校验后缀,使用Apache POI WorkbookFactory解析.xls/.xlsx,按行读取并映射为领域对象,落库,返回成功条数与失败原因。
  • 数据导出:按查询条件或ids查询数据,转换为VO,使用自研或第三方工具写出到HttpServletResponse,支持大数据量分 Sheet 写入。
  • 关键关注点:
    • 模板路径与资源加载(建议使用ClassPathResource)。
    • 导入时单元格取值的“空值/类型”安全处理。
    • 关联字典(如应用领域)需做“名称→ID”的容错查询。
    • 导出时中文文件名编码与多浏览器兼容(建议RFC 2231方式)。
    • 资源关闭与异常兜底,避免连接/句柄泄漏。

二 后端实现要点与代码

  • 模板下载 Controller
@Operation(summary="数据导入")@GetMapping("/downloadApplicationStandardBatchTemplate")publicvoiddownloadApplicationStandardBatchTemplate(HttpServletResponseresponse){BufferedInputStreambis=null;BufferedOutputStreambos=null;try{response.reset();response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");StringfileName="application_standard_batch.xlsx";// 兼容中文文件名:RFC 2231StringencodedFilename=URLEncoder.encode(fileName,StandardCharsets.UTF_8).replaceAll("\\+","%20");response.setHeader("Content-Disposition","attachment; filename=\""+encodedFilename+"\"; filename*=UTF-8''"+encodedFilename);response.setHeader("Access-Control-Expose-Headers","Content-Disposition");Resourceresource=newClassPathResource("/applicationStandard/"+fileName);try(InputStreamin=resource.getInputStream();ServletOutputStreamout=response.getOutputStream()){bis=newBufferedInputStream(in);bos=newBufferedOutputStream(out);byte[]buff=newbyte[2048];intbytesRead;while((bytesRead=bis.read(buff))!=-1){bos.write(buff,0,bytesRead);}bos.flush();}}catch(Exceptione){// 建议统一异常处理(全局异常处理器),便于监控与告警log.error("下载模板失败",e);response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);}finally{// try-with-resources 已关闭流,这里兜底IOUtils.closeQuietly(bis);IOUtils.closeQuietly(bos);}}
  • 批量导入 Controller(含字典映射与校验)
@Operation(summary="标准数据模板批量导入-v1.0")@PostMapping("/import")publicCommonResultimportCase(@RequestParam("uploadFile")MultipartFilefile){LoginUserloginUser=SecurityFrameworkUtils.getLoginUser();if(loginUser==null){returnCommonResult.error(BAD_REQUEST.getCode(),"非法操作");}if(file==null||file.isEmpty()){returnCommonResult.error(BAD_REQUEST.getCode(),"批量导入的Excel文件不能为空");}StringfileName=file.getOriginalFilename();if(!ImageUtils.checkExcel(fileName)){returnCommonResult.error(BAD_REQUEST.getCode(),"上传Excel后缀不符合要求");}try(InputStreamis=file.getInputStream()){Workbookworkbook=WorkbookFactory.create(is);Sheetsheet=workbook.getSheetAt(0);introwCount=sheet.getPhysicalNumberOfRows();if(rowCount<=1){returnCommonResult.error(BAD_REQUEST.getCode(),"模板无数据");}intsuccessNum=0;for(inti=1;i<rowCount;i++){// 第0行为表头Rowrow=sheet.getRow(i);if(row==null)continue;SesApplicationStandardContententity=newSesApplicationStandardContent();Stringv0=getCellString(row.getCell(0));if(StringUtils.hasText(v0))entity.setStandardName(v0);Stringv1=getCellString(row.getCell(1));if(StringUtils.hasText(v1))entity.setStandardVersion(v1);Stringv2=getCellString(row.getCell(2));if(StringUtils.hasText(v2))entity.setTestItem(v2);Stringv3=getCellString(row.getCell(3));if(StringUtils.hasText(v3))entity.setTestPort(v3);Stringv4=getCellString(row.getCell(4));if(StringUtils.hasText(v4))entity.setTestLevel(v4);StringenvName=getCellString(row.getCell(5));if(StringUtils.hasText(envName)){SesApplicationEnvironmentenv=newSesApplicationEnvironment();env.setEnvironmentName(envName);List<SesApplicationEnvironment>list=sesApplicationEnvironmentService.findSelect(env);if(CollectionUtils.isNotEmpty(list)){entity.setSesApplicationEnvironmentId(list.get(0).getId());}else{// 可选:记录“未匹配到应用领域”的错误信息,便于导入回执}}Stringv6=getCellString(row.getCell(6));if(StringUtils.hasText(v6))entity.setBaseStandard(v6);Stringc1=getCellString(row.getCell(10));if(StringUtils.hasText(c1))entity.setProductClassOne(c1);Stringc2=getCellString(row.getCell(11));if(StringUtils.hasText(c2))entity.setProductClassTwo(c2);Stringc3=getCellString(row.getCell(12));if(StringUtils.hasText(c3))entity.setProductClassThree(c3);Stringv13=getCellString(row.getCell(13));if(StringUtils.hasText(v13))entity.setTestPortFeature(v13);Stringv14=getCellString(row.getCell(14));if(StringUtils.hasText(v14))entity.setMaintenanceResponsiblePerson(v14);sesApplicationStandardContentService.insertSesApplicationStandardContent(entity);successNum++;}returnCommonResult.success("导入成功,共 "+successNum+" 条");}catch(EncryptedDocumentExceptione){log.error("导入Excel文件加密或格式异常",e);returnCommonResult.error(BAD_REQUEST.getCode(),"Excel文件无法解析(可能加密)");}catch(IOExceptione){log.error("导入Excel文件IO异常",e);returnCommonResult.error(BAD_REQUEST.getCode(),"Excel文件读取失败");}}// 安全读取单元格为字符串(容错空/数字/日期等)privateStringgetCellString(Cellcell){if(cell==null)returnnull;returnnewDataFormatter().formatCellValue(cell).trim();}
  • 数据导出 Controller
@Operation(summary="EMC应用标准测试内容-导出-v1.0")@GetMapping("/export")publicvoidexport(SesApplicationStandardContentcondition,@RequestParam(required=false)Stringids,HttpServletResponseresponse)throwsIOException{List<SesApplicationStandardContent>list;if(StringUtils.hasText(ids)){List<Long>idList=Arrays.stream(Convert.toStrArray(",",ids)).filter(StringUtils::hasText).map(Long::valueOf).toList();list=sesApplicationStandardContentService.selectSesApplicationStandardContentByIds(idList);}else{list=sesApplicationStandardContentService.selectSesApplicationStandardContentList(condition);}List<SesApplicationStandardContentVO>voList=list.stream().map(src->{SesApplicationStandardContentVOvo=newSesApplicationStandardContentVO();BeanUtils.copyProperties(src,vo);if(src.getSesApplicationEnvironmentId()!=null){SesApplicationEnvironmentenv=sesApplicationEnvironmentService.getById(src.getSesApplicationEnvironmentId());vo.setEnvironmentName(env!=null?env.getEnvironmentName():null);}returnvo;}).toList();ExcelUtil<SesApplicationStandardContentVO>util=newExcelUtil<>(SesApplicationStandardContentVO.class);util.exportExcelToResponse(voList,"EMC应用标准测试内容数据",response);}
  • 导出到响应的通用工具方法(支持大数据量分 Sheet)
public<T>voidexportExcelToResponse(List<T>list,StringsheetName,HttpServletResponseresponse)throwsIOException{if(CollectionUtils.isEmpty(list)){response.setStatus(HttpServletResponse.SC_NO_CONTENT);return;}// 初始化:创建 Workbook、设置字段、分页/分 Sheet 参数(sheetSize 自定义)this.init(list,sheetName,Excel.Type.EXPORT);doublesheetNo=Math.ceil((double)list.size()/sheetSize);for(inti=0;i<=sheetNo;i++){createSheet(sheetNo,i);Rowheader=sheet.createRow(0);intcol=0;for(Object[]os:fields){Excelexcel=(Excel)os[1];createCell(excel,header,col++);}if(Excel.Type.EXPORT.equals(type)){fillExcelData(index,header);addStatisticsRow();}}// 文件名编码与响应头StringencodedFilename=URLEncoder.encode(sheetName,StandardCharsets.UTF_8).replaceAll("\\+","%20")+".xlsx";response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setHeader("Content-Disposition","attachment; filename=\""+encodedFilename+"\"; filename*=UTF-8''"+encodedFilename);response.setHeader("Access-Control-Expose-Headers","Content-Disposition");try(ServletOutputStreamout=response.getOutputStream()){wb.write(out);}finally{if(wb!=null)wb.close();}}

三 前端实现要点与代码

  • 导出按钮(支持按选中 ID 或全量条件)
handleExport(){constqueryParams=addSESDateRange(this.queryParams,this.dateRange,this.updateDateRange)if(this.ids&&this.ids.length>0){queryParams.ids=this.ids.join(',')}else{// 导出全部时移除分页参数Object.keys(queryParams).forEach(key=>{if(['pageNum','pageSize'].includes(key))deletequeryParams[key]})}constmsg=this.ids?.length?`确认导出选中的${this.ids.length}条数据项?`:'确认导出所有符合条件的数据项?'ElMessageBox.confirm(msg,'警告',{type:'warning'}).then(()=>exportSesApplicationStandardContent(queryParams)).then(res=>{if(res&&res.size>0){download.excel(res,'应用标准测试内容.xlsx')ElMessage.success('导出成功')}else{ElMessage.error('导出失败,返回数据为空')}}).catch(err=>{console.error(err)ElMessage.error('导出失败,请检查网络或联系管理员')})}
  • 批量导入弹窗(示例)
handleUpload(){this.uploadDialog.visible=true}

四 常见问题与优化建议

  • 模板下载中文文件名乱码
    • 使用URLEncoder.encode(…)+filename=UTF-8’'* 的RFC 2231方式,兼容主流浏览器;避免使用ISO-8859-1转码。
  • 导入时单元格取值异常
    • 使用DataFormatter统一将单元格格式化为字符串,避免数字/日期类型导致的取值问题;对null单元格做兜底。
  • 导入性能与内存
    • 大数据量时,建议采用SAX/事件模式EasyExcel进行流式读取,分批入库,避免OOM
  • 关联字典容错
    • 对“应用领域”等字典字段,名称→ID 查询无结果时记录错误明细,支持导入回执与失败重试。
  • 导出大数据量
    • 采用分Sheet写入、分页查询、流式输出,避免一次性将全部数据装入内存。
  • 安全性
    • 校验文件类型/大小、限制上传并发、校验登录态与权限;对导入模板做版本管理,避免结构变化导致解析失败。
  • 可观测性
    • 完善导入/导出日志与失败明细,接入告警;提供导入结果统计(成功/失败/原因)下载。

五 依赖与配置建议

  • 核心依赖(示例)
<!-- Apache POI --><dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>5.2.5</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>5.2.5</version></dependency><!-- 可选:EasyExcel(大数据量导入导出更省内存) --><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.3.3</version></dependency>
  • 配置建议
    • 上传大小限制:spring.servlet.multipart.max-file-size / max-request-size
    • 静态资源与模板:将模板放入src/main/resources/applicationStandard/,确保打包后位于classpath
    • 统一异常处理:使用@ControllerAdvice捕获 Excel 解析/IO 异常,返回标准错误码与提示

以上文档覆盖了从模板下载、批量导入到数据导出的完整链路,并给出了关键代码示例与优化方向。后续可结合 EasyExcel 或自研模板引擎,进一步提升可维护性与性能。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/14 11:47:16

Android16 3576 u盘固件升级功能添加

在RK3576 Android16的大屏项目,实现接入U盘升级方式如下: 1.在u-boot/drivers/power/pmic/rk8xx.c 检测到power键长按逻辑然后进行升级: diff --git a/drivers/power/pmic/rk8xx.c b/drivers/power/pmic/rk8xx.c index a3c828a..7cddc74 100644 --- a/drivers/power/pmic/rk…

作者头像 李华
网站建设 2026/4/15 9:58:43

泰克示波器租赁:工程师与项目团队的高效成本解决方案

在研发测试、生产维修和科研教育领域&#xff0c;一台高性能示波器往往是不可或缺的关键工具。然而&#xff0c;面对泰克&#xff08;Tektronix&#xff09;这类高端品牌示波器动辄数十万甚至上百万元的购置成本、复杂的选型以及后续的维护压力&#xff0c;许多工程师、初创团队…

作者头像 李华
网站建设 2026/4/18 4:25:00

SIGSEGV段错误排查全攻略

您遇到的 SIGSEGV&#xff08;段错误&#xff09;是程序试图访问非法内存地址的典型表现。以下是系统性的排查和解决方法&#xff1a; 1. 核心原因分析 非法内存访问&#xff1a;如访问未分配的内存、已释放的内存、只读内存&#xff08;如字符串常量&#xff09;、数组越界、…

作者头像 李华
网站建设 2026/4/11 18:22:21

职场真相:会干是底气,会说是能力

“我只要把活干好&#xff0c;总会被看到的”——很多职场人都有过这样的想法&#xff0c;抱着“酒香不怕巷子深”的心态&#xff0c;默默付出却不善表达&#xff0c;最终看着不如自己的人得到晋升机会&#xff0c;只能暗自委屈。这种“被动等待被发现”的思维&#xff0c;本质…

作者头像 李华
网站建设 2026/4/18 0:08:15

大模型/人工智能高薪职位全解析,助你规划技术发展之路

本文是一份AI大模型领域的高薪职位招聘汇总&#xff0c;包含AI大模型架构师、性能优化专家、算法专家等多个职位&#xff0c;遍布全国主要城市&#xff0c;薪资普遍在80-300W。这些职位要求应聘者具备深厚的机器学习、深度学习理论基础&#xff0c;熟悉大模型架构和训练优化技术…

作者头像 李华
网站建设 2026/4/12 15:55:05

Flutter 开发的鸿蒙AtomGit OAuth 授权应用

Flutter 开发的鸿蒙AtomGit OAuth 授权应用 项目概述 这是一个基于 Flutter 开发的鸿蒙OAuth 2.0 授权应用&#xff0c;用于获取 AtomGit 授权用户的个人信息。应用实现了完整的 OAuth 授权流程&#xff0c;包括&#xff1a; ✅ OAuth 2.0 授权码模式✅ 访问令牌获取和刷新✅…

作者头像 李华