当前位置: 首页 > news >正文

智慧社区(十一)——Spring Boot 实现 Excel 导出、上传与数据导入全流程详解

一、需求分析

本次需要实现三个核心功能:

  1. Excel 导出:根据分页、查询条件(用户名、小区 ID、手机号)查询居民信息,生成 Excel 文件并返回文件名。
  2. Excel 上传:接收前端上传的 Excel 文件,保存到服务器指定路径,返回生成的唯一文件名。
  3. 数据导入:读取已上传的 Excel 文件,解析数据并验证,最终将有效数据存入数据库。

二、技术选型

  • 框架:Spring Boot 2.x
  • Excel 处理:Apache POI(用于操作 Excel 文件,支持 xls 格式)
  • 文件命名:UUID(生成唯一文件名,避免冲突)
  • 数据校验:MyBatis-Plus(简化数据库操作与查询)
  • 依赖管理:Maven

核心依赖(Apache POI 相关,已包含在 easypoi-starter 中):

<dependency><groupId>cn.afterturn</groupId><artifactId>easypoi-spring-boot-starter</artifactId><version>4.2.0</version>
</dependency>

三、Excel 导出实现

1. 实现思路

  • 准备 Excel 模板(含固定表头,如居民 ID、小区名称等)
  • 根据查询条件获取居民数据
  • 复制模板文件(避免修改原模板),生成带时间戳的新文件
  • 使用 Apache POI 向新文件写入数据
  • 返回生成的文件名

2. 代码实现

(1)Controller 层

接收前端参数,调用服务层查询数据,再通过工具类生成 Excel:

/*** 文件导出接口* @param personListForm 包含分页和查询条件的表单* @return 包含生成的Excel文件名的响应*/
@GetMapping("/exportExcel")
public Result exportExcel(PersonListForm personListForm){// 调用服务层查询居民数据(分页)PageVO pageVO = this.personService.personList(personListForm);List<PersonVO> personList = pageVO.getList();// 调用工具类生成Excel,excel为预设的文件存储路径String fileName = ExcelUtil.ExpPersonInfo(personList, excel);return Result.ok().put("data", fileName);
}
(2)Excel 工具类(核心)

负责复制模板、写入数据、设置样式:

public class ExcelUtil {/*** 导出居民信息到Excel* @param personList 居民数据列表* @param basePath 文件存储根路径* @return 生成的Excel文件名*/public static String ExpPersonInfo(List<PersonVO> personList, String basePath){POIFSFileSystem fs = null;// 表头所在行数(模板中前2行为表头,从第3行开始写入数据)int headRow = 2;String generatedFileName = null;try {// 1. 复制模板文件(避免修改原模板)String templatePath = basePath + "personInfo.xls"; // 模板路径Date date = new Date();SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");String timeStamp = sdf.format(date);generatedFileName = timeStamp + ".xls"; // 生成带时间戳的新文件名String targetPath = basePath + generatedFileName;// 复制文件流FileInputStream fis = new FileInputStream(templatePath);FileOutputStream fos = new FileOutputStream(targetPath);byte[] buffer = new byte[1024 * 4];int len;while ((len = fis.read(buffer)) != -1) {fos.write(buffer, 0, len);}fis.close();fos.close();// 2. 向新文件写入数据fs = new POIFSFileSystem(new FileInputStream(targetPath));HSSFWorkbook workbook = new HSSFWorkbook(fs); // 打开Excel工作簿HSSFSheet sheet = workbook.getSheetAt(0); // 获取第一个工作表// 设置单元格样式(边框)HSSFCellStyle cellStyle = workbook.createCellStyle();cellStyle.setBorderLeft(BorderStyle.THIN);cellStyle.setBorderRight(BorderStyle.THIN);cellStyle.setBorderTop(BorderStyle.THIN);cellStyle.setBorderBottom(BorderStyle.THIN);// 遍历数据列表,写入行for (int i = 0; i < personList.size(); i++) {PersonVO person = personList.get(i);HSSFRow row = sheet.createRow(i + headRow); // 从表头后开始创建行int col = 0;// 按顺序写入单元格(与模板表头对应)HSSFCell cell = row.createCell(col++);cell.setCellStyle(cellStyle);cell.setCellValue(person.getPersonId());cell = row.createCell(col++);cell.setCellStyle(cellStyle);cell.setCellValue(person.getCommunityName());// 省略其他字段(小区名称、楼栋、房号、姓名、性别等)...}// 3. 保存文件FileOutputStream out = new FileOutputStream(targetPath);workbook.write(out);out.close();} catch (Exception e) {e.printStackTrace();}return generatedFileName;}
}

3. 关键说明

  • 模板复用:通过复制模板文件,避免每次导出修改原模板,保证模板稳定性。
  • 样式统一:设置单元格边框样式,使导出的 Excel 格式更规范。
  • 文件名唯一:使用时间戳命名,确保每次导出的文件不重复。

四、Excel 上传实现

1. 实现思路

  • 接收前端上传的 MultipartFile 文件
  • 验证文件是否为空
  • 生成 UUID 唯一文件名(避免重名)
  • 保存文件到服务器指定路径
  • 返回生成的文件名(用于后续导入操作)

2. 代码实现

/*** Excel上传接口* @param file 上传的Excel文件* @return 包含生成的文件名的响应* @throws Exception 异常处理*/
@PostMapping("/excelUpload")
public Result excelUpload(@RequestParam("uploadExcel") MultipartFile file) throws Exception {// 验证文件是否为空if (file.getOriginalFilename().isEmpty()) {return Result.error("未选中上传文件");}// 生成唯一文件名(UUID + 原文件后缀)String originalFileName = file.getOriginalFilename();String fileSuffix = originalFileName.substring(originalFileName.lastIndexOf(".")); // 获取后缀(如.xls)String newFileName = UUID.randomUUID().toString() + fileSuffix;// 保存文件到指定路径(excel为预设的存储路径)File targetFile = new File(excel, newFileName);file.transferTo(targetFile);return Result.ok().put("data", newFileName);
}

3. 关键说明

  • 文件验证:防止空文件上传导致的错误。
  • 唯一命名:使用 UUID 避免文件名冲突,确保文件存储安全。
  • 路径管理:建议将文件存储路径配置在 application.properties 中,便于维护。

五、数据导入实现

1. 实现思路

  • 从 Session 获取当前登录用户(用于记录数据创建者)
  • 根据上传时返回的文件名,读取服务器上的 Excel 文件
  • 使用 Apache POI 解析 Excel 数据(跳过表头行)
  • 验证数据合法性(如小区名称是否存在)
  • 将有效数据存入数据库
  • 返回导入结果(成功 / 失败信息)

2. 代码实现

/*** 数据导入接口* @param fileName 上传的Excel文件名* @param session 用于获取当前登录用户* @return 导入结果响应*/
@LogAnnotation("导入数据")
@PostMapping("/parsefile/{fileName}")
public Result parsefile(@PathVariable("fileName") String fileName, HttpSession session){// 获取当前登录用户(用于设置数据创建者)User currentUser = (User) session.getAttribute("user");if (currentUser == null) {return Result.error("请先登录");}POIFSFileSystem fs = null;HSSFWorkbook workbook = null;try {// 读取服务器上的Excel文件String filePath = excel + fileName;fs = new POIFSFileSystem(new FileInputStream(filePath));workbook = new HSSFWorkbook(fs);} catch (Exception e) {e.printStackTrace();return Result.error("文件读取失败");}// 获取第一个工作表HSSFSheet sheet = workbook.getSheetAt(0);int totalRows = sheet.getLastRowNum() + 1; // 总行数(含表头)int totalCols = sheet.getRow(0).getLastCellNum(); // 总列数int headRow = 2; // 表头行数(前2行为表头,从第3行开始读取数据)// 存储解析后的数据Object[][] data = new Object[totalRows - headRow][totalCols];DataFormatter dataFormatter = new DataFormatter(); // 用于格式化单元格数据为字符串// 解析Excel数据for (int i = headRow; i < totalRows; i++) {HSSFRow row = sheet.getRow(i);for (int j = 0; j < totalCols; j++) {HSSFCell cell = row.getCell(j);if (cell == null) {data[i - headRow][j] = "";continue;}// 格式化单元格数据(处理数字、日期等类型)data[i - headRow][j] = dataFormatter.formatCellValue(cell);}}// 验证并保存数据到数据库String errorInfo = "";int headRowNum = 3; // 错误提示中的行号(Excel实际行号,从1开始)for (int i = 0; i < data.length; i++) {Person person = new Person();person.setState(1); // 默认状态为启用person.setFaceUrl(""); // 头像默认为空person.setCreater(currentUser.getUsername()); // 设置创建者try {int col = 1; // 从第2列开始读取(对应模板中的小区名称)// 验证小区名称是否存在String communityName = data[i][col++].toString();QueryWrapper<Community> queryWrapper = new QueryWrapper<>();queryWrapper.eq("community_name", communityName);Community community = communityService.getOne(queryWrapper);if (community == null) {errorInfo += "第" + (i + headRowNum) + "行:小区名称不存在;";continue; // 跳过当前行}person.setCommunityId(community.getCommunityId());// 填充其他字段(楼栋、房号、姓名、性别、手机号等)person.setTermName(data[i][col++].toString());person.setHouseNo(data[i][col++].toString());person.setUserName(data[i][col++].toString());person.setSex(data[i][col++].toString());person.setMobile(data[i][col++].toString());person.setPersonType(data[i][col++].toString());person.setRemark(data[i][col++].toString());// 保存到数据库personService.save(person);} catch (Exception e) {errorInfo += "第" + (i + headRowNum) + "行:数据格式错误;";e.printStackTrace();}}if (!errorInfo.isEmpty()) {return Result.ok().put("status", "fail").put("data", errorInfo);} else {return Result.ok().put("status", "success").put("data", "数据导入完成!");}
}

3. 关键说明

  • 数据解析:使用 DataFormatter 处理单元格数据,避免数字、日期等类型转换错误。
  • 数据验证:导入前验证小区名称是否存在,避免无效数据入库。
  • 错误处理:记录错误行号和原因,便于用户修正后重新导入。
  • 用户关联:通过 Session 获取当前用户,记录数据创建者,便于数据溯源。

http://www.dtcms.com/a/324517.html

相关文章:

  • Langchain调用MCP服务和工具
  • MySQL的逻辑架构和SQL执行的流程:
  • 正确使用SQL Server中的Hint(10)—Hint简介与Hint分类及语法(1)
  • Spring Boot + SSH 客户端:在浏览器中执行远程命令
  • 深入理解 Java 中的线程池:原理、参数与最佳实践
  • 【密码学】8. 密码协议
  • 金融机构在元宇宙中的业务开展与创新路径
  • 【教学类-29-06】20250809灰色门牌号-黏贴版(6层*5间层2间)题目和答案(剪贴卡片)
  • 使用Python调用OpenAI的function calling源码
  • Pytorch深度学习框架实战教程-番外篇02-Pytorch池化层概念定义、工作原理和作用
  • ROS2 QT 多线程功能包设计
  • PHP项目运行
  • (LeetCode 每日一题) 869. 重新排序得到 2 的幂 (哈希表+枚举)
  • Framework开发之Zygote进程2(基于开源的AOSP15)--init.rc在start zygote之后的事情(详细完整版逐行代码走读)
  • springboot骚操作
  • 【论文阅读】Deep Adversarial Multi-view Clustering Network
  • 视觉障碍物后处理
  • Java开发异步编程中常用的接口和类
  • 人工智能之数学基础:如何理解n个事件的独立?
  • [C/C++线程安全]_[中级]_[避免使用的C线程不安全函数]
  • Android APK 使用OpenGl 绘制三角形源码
  • Word XML 批注范围克隆处理器
  • 绕过文件上传漏洞并利用文件包含漏洞获取系统信息的技术分析
  • 使用MongoDB存储和计算距离
  • Spring Boot 2 升级 Spring Boot 3 的全方位深度指南
  • Leetcode 3644. Maximum K to Sort a Permutation
  • 9_基于深度学习的车型检测识别系统(yolo11、yolov8、yolov5+UI界面+Python项目源码+模型+标注好的数据集)
  • Error: error:0308010C:digital envelope routines::unsupported at new Hash
  • 【Python 小脚本·大用途 · 第 3 篇】
  • 编译xformers