【Excel】使用 SpringBoot 实现 Excel 文件的导入与导出

您所在的位置:网站首页 pdf批量导入excel 【Excel】使用 SpringBoot 实现 Excel 文件的导入与导出

【Excel】使用 SpringBoot 实现 Excel 文件的导入与导出

2024-03-18 15:18| 来源: 网络整理| 查看: 265

为了大家能更好地理解这篇博文,在看之前,建议去看看我的上一篇博文:【File】使用 SpringBoot 实现文件的上传与下载。因为,这篇博文是在上一篇博文基础之上继续开发的。

这两篇博文互有联系:文件的导入就是文件的上传;文件的导出就是文件的下载。

至于为什么是 导入/导出 Excel 文件呢?因为 Excel 文件用的频率更多吧。顺便来复习下通过 EasyExcel 来操作 Excel 文件。

编码思路:

Excel 导入:

浏览文件夹,选择需要上传的 Excel 文件,这里使用 POSTMAN 工具;将本地文件上传至服务器指定位置;服务器解析Excel文件;将Excel中解析的数据存入数据库中。

Excel 导出

设定查询条件;数据库中查询相应的数据 ;将数据写入Excel;将 Excel 下载至本地。

导入 POM 依赖:

com.alibaba easyexcel 3.2.1 com.alibaba fastjson 1.2.5 1. Excel 导入

FileController:添加一个导入接口

@RestController @RequestMapping("/file") public class FileController { @Autowired private FileService fileService; @PostMapping("/importExcel") public ResultVo importExcel(@RequestParam("file") MultipartFile excel) { return fileService.importExcel(excel); } }

FileServiceImpl:

@Service @Slf4j public class FileServiceImpl implements FileService { @Autowired private ExcelUtil excelUtil; @Override public ResultVo importExcel(MultipartFile file) { // 1.可做入参校验,这里省略 // 2.上传至服务器某路径下 ResultVo resultVo = uploadFile(file); if (!resultVo.checkSuccess()) { return resultVo; } String filePath = (String)resultVo.getData(); if (StringUtil.isBlank(filePath)) { return ResultVoUtil.error("【导入Excel文件】生成的Excel文件的路径为空"); } // 3.读取excel文件 List excelVos = excelUtil.simpleExcelRead(filePath, ExcelVo.class); if (CollectionUtil.isEmpty(excelVos) || excelVos.size() log.error("【导入Excel文件】删除临时文件失败,临时文件路径为{}", filePath); return ResultVoUtil.error("删除临时文件失败"); } log.info("【导入Excel文件】删除临时文件成功,临时文件路径为:{}", filePath); return ResultVoUtil.success(excelVos); } // 上传文件 public ResultVo uploadFile(MultipartFile file) { log.info("【文件上传】进入到文件上传方法"); // 1.参数校验 if (null == file || file.isEmpty()) { log.error("【文件上传】文件为空!"); throw new ParamErrorException(); } // 2.上传文件 ResultVo resultVo = FileUtil.uploadFile(file); return resultVo; } } public class FileUtil { // 下划线 public static final String UNDER_LINE = "_"; // 上传文件 public static ResultVo uploadFile(MultipartFile file) { // 1.获取一个新的文件名 String newFileName = getNewFileName(file); if (StringUtil.isBlank(newFileName)) { log.error("【上传文件】转换文件名称失败"); return ResultVoUtil.error("【上传文件】转换文件名称失败"); } // 2.获取文件上传路径 String uploadPath = "E:\\temp"; if (StringUtil.isBlank(uploadPath)) { log.error("【上传文件】获取文件上传路径失败"); return ResultVoUtil.error("【上传文件】获取文件上传路径失败"); } uploadPath = uploadPath + File.separator + DateUtil.getCurrentDate(); // 3.生成上传目录 File uploadDir = mkdirs(uploadPath); if (!uploadDir.exists()) { log.error("【上传文件】生成上传目录失败"); return ResultVoUtil.error("【上传文件】生成上传目录失败"); } // 4.文件全路径 String fileFullPath = uploadPath + File.separator + newFileName; log.info("上传的文件:" + file.getName() + "," + file.getContentType() + ",保存的路径为:" + fileFullPath); try { // 5.上传文件 doUploadFile(file, fileFullPath); } catch (IOException e) { log.error("【上传文件】上传文件报IO异常,异常信息为{}", e.getMessage()); return ResultVoUtil.error(e.getMessage()); } return ResultVoUtil.success(fileFullPath); } /** * @Description:上传文件 * @Author: zzc * @Date: 2021-11-11 0:05 * @param file: * @param path: * @return: void **/ public static void doUploadFile(MultipartFile file, String path) throws IOException { // 法一: Streams.copy(file.getInputStream(), new FileOutputStream(path), true); // 法二: 通过MultipartFile#transferTo(File) // 使用此方法保存,必须要绝对路径且文件夹必须已存在,否则报错 //file.transferTo(new File(path)); // 法三:通过NIO将字节写入文件 //Path filePath = Paths.get(path); //Files.write(filePath, file.getBytes()); // 法四: /*try (InputStream in = file.getInputStream(); FileOutputStream out = new FileOutputStream(path)) { IOUtils.copy(in, out); } catch (Exception e) { log.error("【上传文件】上传文件失败,失败信息为:{}", e.getMessage()); }*/ // 法五: /*InputStream in = file.getInputStream(); OutputStream out = new FileOutputStream(path); int len = 0; byte[] bytes = new byte[1024]; while ((len = in.read(bytes)) != -1) { out.write(bytes, 0, len); } in.close(); out.close();*/ // 法六: /*byte[] bytes = file.getBytes(); OutputStream out = new FileOutputStream(path); out.write(bytes); out.close();*/ } /** * @Description:递归生成父级路径 * @Author: zzc * @Date: 2021-11-10 23:55 * @param path: * @return: void **/ public static File mkdirs(String path) { File file = new File(path); if(!file.exists() || !file.isDirectory()) { file.mkdirs(); } return file; } /** * @Description:将上传的文件转换为一个新的文件名 * @Author: zzc * @Date: 2021-11-10 23:17 * @param file: * @return: java.lang.String **/ public static String getNewFileName(MultipartFile file) { // 1.获取上传的文件名称(包含后缀。如:test.jpg) String originalFilename = file.getOriginalFilename(); log.info("【上传文件】上传的文件名为{}", originalFilename); // 2.以小数点进行分割 String[] split = originalFilename.split("\\."); String newFileName = null; if (null == split || split.length == 0) { return null; } StringBuilder builder = new StringBuilder(); if (1 == split.length) { // 3.此文件无后缀 newFileName = builder.append(originalFilename).append(UNDER_LINE).append(System.nanoTime()).toString(); return newFileName; } // 4.获取文件的后缀 String fileSuffix = split[split.length - 1]; for (int i = 0; i builder.append(UNDER_LINE); } } newFileName = builder.append(System.nanoTime()).append(".").append(fileSuffix).toString(); return newFileName; } /** * @Description:下载文件 * @Author: zzc * @Date: 2021-11-12 0:01 * @param file: * @param response: * @return: com.zzc.hardcore.vo.ResultVo **/ public static ResultVo downloadFile(File file, HttpServletResponse response) { try { // 1.设置响应头 setResponse(file, response); } catch (UnsupportedEncodingException e) { log.error("文件名{}不支持转换为字符集{}", file.getName(), "UTF-8"); return ResultVoUtil.error(e.getMessage()); } // 2.下载文件 return doDownLoadFile(file, response); } /** * @Description:下载文件 * @Author: zzc * @Date: 2021-11-11 22:51 * @param file: * @param response: * @return: **/ public static ResultVo doDownLoadFile(File file, HttpServletResponse response) { // 法一:IOUtils /*try (FileInputStream in = new FileInputStream(file); OutputStream out = response.getOutputStream()) { // 2.下载文件 IOUtils.copy(in, out); log.info("【文件下载】文件下载成功"); return null; } catch (FileNotFoundException e) { log.error("【文件下载】下载文件时,没有找到相应的文件,文件路径为{}", file.getAbsolutePath()); return ResultVoUtil.error(e.getMessage()); } catch (IOException e) { log.error("【文件下载】下载文件时,出现文件IO异常"); return ResultVoUtil.error(e.getMessage()); }*/ // 法二:将文件以流的形式一次性读取到内存,通过响应输出流输出到前端 /*try (InputStream in = new BufferedInputStream(new FileInputStream(file)); OutputStream out = new BufferedOutputStream(response.getOutputStream())) { byte[] buffer = new byte[in.available()]; in.read(buffer); out.write(buffer); log.info("【文件下载】文件下载成功"); return null; } catch (IOException e) { log.error("【文件下载】下载文件时,出现文件IO异常"); return ResultVoUtil.error(e.getMessage()); }*/ // 法三:将输入流中的数据循环写入到响应输出流中,而不是一次性读取到内存,通过响应输出流输出到前端 try (InputStream in = new FileInputStream(file); OutputStream out = response.getOutputStream()) { byte[] buffer = new byte[1024]; int len = 0; while ((len = in.read(buffer)) != -1) { out.write(buffer, 0, len); } log.info("【文件下载】文件下载成功"); return null; } catch (FileNotFoundException e){ log.error("【文件下载】下载文件时,没有找到相应的文件,文件路径为{}", file.getAbsolutePath()); return ResultVoUtil.error(e.getMessage()); } catch (IOException e) { log.error("【文件下载】下载文件时,出现文件IO异常"); return ResultVoUtil.error(e.getMessage()); } } /** * @Description:设置响应头 * @Author: zzc * @Date: 2021-11-11 22:44 * @param file: * @param response: * @return: void **/ public static void setResponse(File file, HttpServletResponse response) throws UnsupportedEncodingException { // 清空response response.reset(); response.setCharacterEncoding("UTF-8"); // 返回给客户端类型,任意类型 response.setContentType("application/octet-stream"); // Content-Disposition的作用:告知浏览器以何种方式显示响应返回的文件,用浏览器打开还是以附件的形式下载到本地保存 // attachment表示以附件方式下载 inline表示在线打开 "Content-Disposition: inline; filename=文件名.mp3" response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(file.getName(), "UTF-8")); // 告知浏览器文件的大小 response.addHeader("Content-Length", String.valueOf(file.length())); } /** * @Description:递归删除目录下的所有文件及子目录下所有文件 * @Author: zzc * @Date: 2021-11-14 18:34 * @param file: * @return: boolean **/ public static boolean deleteFile(File file) { if (!file.exists()) { return false; } if (file.isDirectory()) { String[] children = file.list(); //递归删除目录中的子目录下 for (int i=0; i return false; } } } // 目录此时为空,可以删除 return file.delete(); } /** * @Description:获取文件下载时生成文件的路径 * @Author: zzc * @Date: 2021-11-14 19:25 * @return: java.lang.String **/ public static String getDownLoadPath() { return fileConfig.getDownloadPath(); } }

说明:

List excelVos = excelUtil.simpleExcelRead(filePath, ExcelVo.class); 你给我一个Excel文件路径,和一个数据类型,我就能将这个Excel文件中的数据封装成一个集合,并返回给你;

ExcelUtil:

@Component @Slf4j public class ExcelUtil { // excel文件后缀 private final static String EXCE_L2003 = "xls"; private final static String EXCEL_2007 = "xlsx"; // 校验文件后缀是否为 xls、xlsx public static boolean checkExcelExtension(MultipartFile excel) { String filename = excel.getOriginalFilename(); if (StringUtil.isBlank(filename)) { log.info("【校验Excel文件后缀】Excel文件名为空"); return false; } int index = filename.lastIndexOf("."); if (index == -1) { log.info("【校验Excel文件后缀】Excel文件名中没有点号"); return false; } String extension = filename.substring(index + 1); return Arrays.asList(EXCE_L2003, EXCEL_2007).contains(extension); } // 读取excel文件 public List simpleExcelRead(String filePath, Class clazz) { ExcelListener excelListener = new ExcelListener(); EasyExcel.read(filePath, clazz, excelListener).sheet().doRead(); List dataList = excelListener.getDataList(); return dataList; } }

说明:

EasyExcel.read(filePath, clazz, excelListener).sheet().doRead(); 会调用 ExcelListener 类中的方法,将它读取到的数据存储在 dataList 集合中;这里 ExcelUtil 类用的是泛型,你要导入什么类型的数据,就传什么类型的数据。我这里导入的是 ExcelVo 类型。

ExcelListener:

@Slf4j public class ExcelListener extends AnalysisEventListener { // 返回读取到的excel中的数据 List dataList = new ArrayList(); public ExcelListener() { } // 每一条数据解析都会来调用 @Override public void invoke(T t, AnalysisContext analysisContext) { log.info("【Excel文件】解析到一条数据{}:", JSON.toJSONString(t)); dataList.add(t); } // 所有数据解析完成了 才会来调用 @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { log.info("【Excel文件】Excel所有数据解析完毕!"); } public List getDataList() { return dataList; } }

ExcelVo:Excel 中数据信息的模板

@Data @NoArgsConstructor @AllArgsConstructor public class ExcelVo { // 姓名 private String name; // 性别 1:女 0:男 @ExcelProperty(converter = SexConvert.class) private Integer sex; // 创建时间 @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒") private String createTime; }

说明:

ExcelVo 中的属性和 Excel 文件对应性别 sex 属性,我们使用了一个转换器。Excel 文件中性别对应的是男、女,但我们要用整形0、1 去接收它,所以,需要自定义一个性别转换器

Excel 内容: 在这里插入图片描述 SexConvert:性别转换器

public class SexConvert implements Converter { @Override public Class supportJavaTypeKey() { return Integer.class; } @Override public CellDataTypeEnum supportExcelTypeKey() { return CellDataTypeEnum.STRING; } // 这里读的时候会调用 @Override public Integer convertToJavaData(CellData cellData, ExcelContentProperty excelContentProperty, GlobalConfiguration globalConfiguration) throws Exception { switch (cellData.getStringValue()) { case "男": return 0; case "女": return 1; default: return 0; } } // 这里写的时候会调用 @Override public CellData convertToExcelData(Integer integer, ExcelContentProperty excelContentProperty, GlobalConfiguration globalConfiguration) throws Exception { switch (integer) { case 1: return new CellData("女"); case 0: return new CellData("男"); default: return new CellData(String.valueOf(integer)); } } }

FileUtil.deleteFile():删除临时文件

// 递归删除目录下的所有文件及子目录下所有文件 public static boolean deleteFile(File file) { if (!file.exists()) { return false; } if (file.isDirectory()) { String[] children = file.list(); //递归删除目录中的子目录下 for (int i=0; i return false; } } } // 目录此时为空,可以删除 return file.delete(); } 2. Excel 导出

FileController:添加一个文件导出接口

@PostMapping("/exportExcel") public ResultVo exportExcel(final HttpServletResponse response) { return fileService.exportExcel(response); }

FileServiceImpl:

@Override public ResultVo exportExcel(HttpServletResponse response) { // 1.根据查询条件获取结果集 List excelWriteVos = getExcelWriteVoListByCondition(); if (CollectionUtil.isEmpty(excelWriteVos)) { log.info("【导出Excel文件】要导出的数据为空,无法导出!"); return ResultVoUtil.success("数据为空"); } // 2.获取要下载Excel文件的路径 ResultVo resultVo = getDownLoadPath(ExcelWriteVo.class, excelWriteVos); if (!resultVo.checkSuccess()) { log.error("【导出Excel文件】获取要下载Excel文件的路径失败"); return resultVo; } // 3.下载Excel文件 String fileDownLoadPath = resultVo.getData(); ResultVo downLoadResultVo = downloadFile(fileDownLoadPath, response); if (null != downLoadResultVo && !downLoadResultVo.checkSuccess()) { log.error("【导出Excel文件】下载文件失败"); return downLoadResultVo; } // 4.删除临时文件 boolean deleteFile = FileUtil.deleteFile(new File(fileDownLoadPath)); if (!deleteFile) { log.error("【导入Excel文件】删除临时文件失败,临时文件路径为{}", fileDownLoadPath); return ResultVoUtil.error("删除临时文件失败"); } log.info("【导入Excel文件】删除临时文件成功,临时文件路径为:{}", fileDownLoadPath); return null; }

getExcelWriteVoListByCondition():根据条件获取要导出的数据

这里本应该通过数据库查询的,我这里直接用假数据了。

public List getExcelWriteVoListByCondition() { List excelWriteVos = new ArrayList(5); excelWriteVos.add(new ExcelWriteVo("zzc", "男", "2021-11-14 20:00:00")); excelWriteVos.add(new ExcelWriteVo("wzc", "女", "2021-11-14 20:00:00")); excelWriteVos.add(new ExcelWriteVo("wxc", "男", "2021-11-14 20:00:00")); return excelWriteVos; }

ExcelWriteVo :Excel 文件导出对应的Vo类

@Data @NoArgsConstructor @AllArgsConstructor public class ExcelWriteVo { // 姓名 @ExcelProperty("姓名") private String name; // 性别 1:女 0:男 @ExcelProperty("性别") private String sex; // 创建时间 @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒") @ExcelProperty("创建时间") private String createTime; }

导出的 Excel: 在这里插入图片描述

getDownLoadPath():获取要下载Excel文件的路径

public ResultVo getDownLoadPath(Class clazz, List excelWriteVos) { String downLoadPath = FileUtil.getDownLoadPath(); if (StringUtil.isBlank(downLoadPath)) { log.error("【导出Excel文件】生成临时文件失败"); return ResultVoUtil.error("生成临时文件失败"); } // 1.创建一个临时目录 FileUtil.mkdirs(downLoadPath); String fullFilePath = downLoadPath + File.separator + System.currentTimeMillis() + "." + ExcelUtil.EXCEL_2007; log.info("【导出Excel文件】文件的临时路径为:{}", fullFilePath); // 2.写入数据 excelUtil.simpleExcelWrite(fullFilePath, clazz, excelWriteVos); return ResultVoUtil.success(fullFilePath); }

application.yml:添加了一个文件下载时生成文件的路径

file: uploadPath: E:/upload downloadPath: E:/download

FileUtil:

// 获取文件下载时生成文件的路径 public static String getDownLoadPath() { return fileConfig.getDownloadPath(); }

ExcelUtil:

@Slf4j public class ExcelUtil { // excel文件后缀 public final static String EXCE_L2003 = "xls"; public final static String EXCEL_2007 = "xlsx"; // sheet名字 public final static String SHEET_NAME = "模板"; // 写Excel文件 public void simpleExcelWrite(String filePath, Class clazz, List dataList) { EasyExcel.write(filePath, clazz).sheet(SHEET_NAME).doWrite(dataList); } }

好了,通过 EasyExcel 文件操作 Excel 就到这了哈。

=========================================================================================== 2021-12-07 更:

使用 easyexcel 设置超链接或附件地址

参考地址

修改 POM 依赖:修改 easyexcel 的版本。2.0.0-beta2 版本无法引入 AbstractCellWriteHandler

com.alibaba easyexcel 2.2.6

ExcelWriteVo:添加一个字段,表示附件

@Data @NoArgsConstructor @AllArgsConstructor public class ExcelWriteVo { // ... // 附件 private String photo; }

FileServiceImpl#getExcelWriteVoListByCondition():修改构造方法

public List getExcelWriteVoListByCondition() { List excelWriteVos = new ArrayList(5); excelWriteVos.add(new ExcelWriteVo("zzc", "男", "2021-11-14 20:00:00", "附件1")); excelWriteVos.add(new ExcelWriteVo("wzc", "女", "2021-11-14 20:00:00", "附件1")); excelWriteVos.add(new ExcelWriteVo("wxc", "男", "2021-11-14 20:00:00", "附件1")); return excelWriteVos; }

添加一个处理器 PhotoHandler:

@Slf4j public class PhotoHandler extends AbstractCellWriteHandler { @Override public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { // 这里可以对cell进行任何操作 log.info("第{}行,第{}列写入完成。", cell.getRowIndex(), cell.getColumnIndex()); if (isHead && cell.getColumnIndex() == 0) { // 对第一行第一列的头超链接 CreationHelper createHelper = writeSheetHolder.getSheet().getWorkbook().getCreationHelper(); Hyperlink hyperlink = createHelper.createHyperlink(HyperlinkType.URL); hyperlink.setAddress("https://github.com/alibaba/easyexcel"); cell.setHyperlink(hyperlink); } if (!isHead && head.getFieldName().equals("photo")) { // 头为 photo 的列设置附件地址 CreationHelper createHelper = writeSheetHolder.getSheet().getWorkbook().getCreationHelper(); Hyperlink hyperlink = createHelper.createHyperlink(HyperlinkType.FILE); // 类型设置为 FILE hyperlink.setAddress("C:/Users/07979/Pictures/1.jpg"); // 附件的地址(相对地址、绝对地址都行) cell.setHyperlink(hyperlink); } } }

导出 Excel 时,需要注册这个处理器:

ExcelUtil#():

@Component @Slf4j public class ExcelUtil { // ... public void simpleExcelWrite(String filePath, Class clazz, List dataList) { EasyExcel.write(filePath, clazz) .registerWriteHandler(new PhotoHandler()) .sheet(SHEET_NAME).doWrite(dataList); } }

说明:

可以注册多个处理器。如: EasyExcel.write(filePath, clazz) .registerWriteHandler(new PhotoHandler()) .registerWriteHandler(new PhotoHandler2()) .sheet(SHEET_NAME).doWrite(dataList);

运行代码后: 在这里插入图片描述 鼠标点击,第一列的头,可跳转;

在这里插入图片描述 鼠标点击“photo” 这一列,也可跳转。

=========================================================================================== 2023-01-09 更:

EasyExcel 导出 Excel 设置单元格文本格式:EasyExcel导出Excel设置单元格文本格式(亲测有效)

只对有数据的行有效:

法1:

@ExcelProperty(value = "创建时间") @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.LEFT, dataFormat = 49) private String createTime;

法2:

public class ExcelCellWriteHandler implements CellWriteHandler { @Override public void afterCellDispose(CellWriteHandlerContext context) { // 3.0 设置单元格为文本 WriteCellData cellData = context.getFirstCellData(); WriteCellStyle writeCellStyle = cellData.getOrCreateStyle(); DataFormatData dataFormatData = new DataFormatData(); dataFormatData.setIndex((short) 49); writeCellStyle.setDataFormatData(dataFormatData); } @Override public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { // 3.0 设置单元格为文本 Workbook workbook = writeSheetHolder.getSheet().getWorkbook(); DataFormat dataFormat = workbook.createDataFormat(); for (WriteCellData writeCellData : cellDataList) { WriteCellStyle writeCellStyle = writeCellData.getOrCreateStyle(); DataFormatData dataFormatData = new DataFormatData(); dataFormatData.setIndex(dataFormat.getFormat("@")); writeCellStyle.setDataFormatData(dataFormatData); } } } public static void simpleExcelWrite(String filePath, Class clazz, List dataList, String sheetName) { EasyExcel.write(filePath, clazz).sheet(sheetName).registerWriteHandler(new ExcelCellWriteHandler()).doWrite(dataList); }


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3