当前位置: 首页 > 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.lryc.cn/news/616299.html

相关文章:

  • 计算机网络:路由聚合的注意事项有哪些?
  • RabbitMQ面试精讲 Day 18:内存与磁盘优化配置
  • ROS2 QT 多线程功能包设计
  • 使用 Docker-Compose 部署 Redis 三主三从集群(含 Exporter 监控)
  • openresty-lua-redis案例
  • 高标准农田建设—用ZDM画钢筋图并标注
  • BLDC直流无刷电机工作原理
  • 【无标题】消息队列(Message Queue)是一种**进程间通信(IPC)机制
  • VBA即用型代码手册:段落Paragraphs
  • GPT-5与中国AI发展(DeepSeek R1视角)
  • 补卡day16
  • Django @login_required实现登陆认证
  • A#语言详解
  • Redis - 使用 Redis HyperLogLog 进行高效基数统计
  • 安全生产基础知识(一)
  • django基于Python的设计师作品平台的数据可视化系统设计与实现
  • 音视频学习(五十二):ADTS
  • 集成电路学习:什么是Parameter Server参数服务器
  • 比特币现货和比特币合约的区别与联系
  • 联邦学习之------VT合谋
  • 刑法视野下的虚拟财产属性争议:法律风险与市场潜力解析
  • 机器学习——TF-IDF 衡量词语在文档中重要程度
  • MySQL 序列使用详细说明
  • SpringIoc 实践和应用--XML配置
  • PHP版本控制系统:高效文档管理
  • MVC结构变种——第三章核心视图及控制器的整体逻辑
  • 计算机网络---IP(互联网协议)
  • 学习分库分表的前置知识:高可用系统架构理论与实践
  • Android视图回调机制:从post到ViewTreeObserver,从源码分析到最佳实践
  • java组件漏洞