SpringBoot文件上传与POI的使用
1.使用springboot上传文件
本文所要源码在一个项目中,源码:https://github.com/zhongyushi-git/springboot-upload-download.git。下面的介绍与源码可能有差别,以源码为准!
1.1环境准备
1)新建一个springboot的项目
2)导入web坐标
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
1.2实战演练
1)创建文件的工具类,在类中添加一个上传的方法
/** * @author zhongyushi * @date 2020/9/29 0029 * @dec 文件工具类,用于上传和下载 */ public class FileUtil { //文件保存的路径 private static final String STORE_PATH = "D://files"; public static String uploadFile(MultipartFile file) { //设置日期的格式 SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd"); //设置文件的保存路径是项目运行目录下的uploadFile目录下 String realPath = new File(STORE_PATH).getAbsolutePath(); //通过日期 对文件归类,如2019/11/30,2019/11/29 String format = File.separator + sdf.format(new Date()); format = format.replace("/", "\\"); //根据规则创建目录 File folder = new File(realPath + format); if (!folder.isDirectory()) { folder.mkdirs(); } //获取文件的原始名 String oldName = file.getOriginalFilename(); //获取文件的后缀名 String suffix = oldName.substring(oldName.lastIndexOf(".")); //使用uuid设置新的文件名,防止文件名重复 String newName = UUID.randomUUID().toString() + suffix; try { //文件保存 file.transferTo(new File(folder, newName)); //生成文件的保存路径 String accessPath = realPath + format + File.separator + newName; return accessPath; } catch (IOException e) { e.printStackTrace(); return e.getMessage(); } } }
2)创建文件上传的接口upload
package com.zys.springbootuploaddownload; import com.zys.springbootuploaddownload.util.FileUtil; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; /** * @author zhongyushi * @date 2020/9/29 0029 * @dec 上传下载的接口 */ @RestController public class TestController { /** * 文件上传,返回文件保存路径 * @param file * @return */ @PostMapping("/upload") public String uploadFile(MultipartFile file){ String path = FileUtil.uploadFile(file); return path; } }
3)在资源目录的static目录下创建一个文件上传的页面upload.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>文件上传</title> </head> <body> <form action="/upload" method="post" enctype="multipart/form-data"> <input type="file" name="file" value="选择文件"> <input type="submit" value="上传"> </form> </body> </html>
注意:按照上面的写法,页面中type是file的name值必须和后台得参数值相同,否则会出现空指针异常。
4)测试
启动项目,在浏览器输入http://localhost:8080/upload.html,选择文件上传,上传成功时会返回文件保存的位置,此时在指定的目录下会生成多级的目录,最后一级是文件。
2.通过浏览器下载文件
1)在文件的工具类添加两个方法
/** * 指定路径下载文件 * @param path 文件路径 * @param filename 文件名,带后缀 * @param response * @throws IOException */ public static void downloadFile(String path, String filename, HttpServletResponse response) throws IOException { File file = new File(path + File.separator + filename); if (!file.exists()) { response.setContentType("text/html;charset=UTF-8"); response.getWriter().write("error"); return; } FileInputStream in = new FileInputStream(file); downloadFile(filename, in, response); } /** * 通过流下载文件 * * @param filename 文件名,带后缀 * @param in 输入流 * @param response * @throws IOException */ public static void downloadFile(String filename, InputStream in, HttpServletResponse response) throws IOException { //设置浏览器直接下载文件,不打开,并设置文件名的编码 response.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8")); int len = 0; byte bytes[] = new byte[1024]; OutputStream out = response.getOutputStream(); while ((len = in.read(bytes)) > 0) { out.write(bytes, 0, len); } in.close(); }
2)添加读取资源目录的工具类
package com.zys.springbootuploaddownload.util; import org.springframework.stereotype.Component; import org.springframework.util.ResourceUtils; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; /** * @author zhongyushi * @date 2020/9/29 0029 * @dec 根据路径获取文件流 */ @Component public class GetFileStream { /** * 获取资源目录下的文件流 * * @param path 文件路径 * @param fileName 文件名,带后缀 * @return */ public InputStream getResourceStream(String path, String fileName) throws FileNotFoundException { InputStream stream = this.getClass().getClassLoader().getResourceAsStream(path + File.separator + fileName); if (stream == null) { throw new FileNotFoundException("路径错误,未找到指定文件"); } return stream; } }
3)在上传的接口添加下载的方法。需要使用@Autowired注入GetFileStream
/** * 从本地下载文件 * @param response * @throws IOException */ @GetMapping("/download1") public void download1(HttpServletResponse response) throws IOException { String path = "D:\\files"; String fileName="123.txt"; FileUtil.downloadFile(path,fileName,response); } /** * 从资源目录下载文件 * @param response * @throws IOException */ @GetMapping("/download2") public void download2(HttpServletResponse response) throws IOException { String path = "templates"; String fileName="a.txt"; InputStream stream = getFileStream.getResourceStream(path, fileName); FileUtil.downloadFile(fileName,stream,response); }
这里有两个方法,一个是直接下载指定路径下的文件,另一个是下载服务器上资源目录下的文件。
3)在static目录下新建文件下载页面file.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>文件下载</title>
</head>
<body>
<a href="/download1">下载本地文件</a>
<a href="/download2">下载服务器文件</a>
</body>
</html>
4)测试
启动项目,在浏览器输入http://localhost:8080/file.html,点击下载即可下载文件。下载之前,代码中指定的文件需要存在对应的位置,否则会下载失败,文件的路径可自定义。
3.poi实现文件导入
3.1环境准备
1)导入poi的依赖
<!-- POI,excel解析相关 --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>3.17</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>3.17</version> </dependency>
2)导入mysql等相关的依赖,配置数据库信息(详见源码)
3)在数据库执行sql脚本。脚本在项目根目录下。
4)导入必需的工具类
package com.zys.springbootuploaddownload.util; import java.io.IOException; import java.io.InputStream; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Properties; /** * 读取.properties配置文件的内容至Map中 */ public class PropertiesUtil { /** * 读取.properties配置文件的内容至Map * * @param propertiesFile * @return * @throws IOException */ public static Map read(String propertiesFile) throws Exception { try { if (!(propertiesFile.indexOf("properties") > 0)) { propertiesFile = propertiesFile + ".properties"; } InputStream inStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(propertiesFile); Properties p = new Properties(); p.load(inStream); Map<Object, Object> map = properties2map(p); return map; } catch (IOException e) { throw new Exception(e); } } /** * 将属性文件转为map * * @param prop * @return */ public static Map properties2map(Properties prop) { Map<Object, Object> map = new HashMap<Object, Object>(); Enumeration enu = prop.keys(); while (enu.hasMoreElements()) { Object obj = enu.nextElement(); Object objv = prop.get(obj); map.put(obj, objv); } return map; } public static Map<String,String> getKeyAndValue(String fileName,String configName) throws Exception { Map<String,String> keyMap=new HashMap<>(); Map<String, String> map = PropertiesUtil.read("importConfig.properties"); //过滤文件内容,只截取student的配置 for (String key : map.keySet()) { if (key.startsWith("student.")) { String[] split = key.split("student."); keyMap.put(map.get(key), split[1]); } } return keyMap; } }
此工具类用来读取指定的配置文件的内容。
package com.zys.springbootuploaddownload.util; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.zys.springbootuploaddownload.entity.ExcelData; import com.zys.springbootuploaddownload.entity.Student; import org.apache.poi.hssf.usermodel.HSSFDateUtil; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.apache.poi.xssf.usermodel.XSSFSheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.net.URLEncoder; import java.text.DateFormat; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.*; /** * @Author: yushizhong * @Date: 2020/2/22 12:07 * @Title: 解析excel内容+导出excel * 解析excel:调用readExcel方法 * 导出excel:浏览器下载调用exportExcel方法 * 本地下载调用generateExcel方法 */ public class ExcelUtil { public final static String XLSX = ".xlsx"; public final static String XLS = ".xls"; /** * 获取Excel文件(.xls和.xlsx都支持) * * @param path 文件全路径 * @return 解析excle后的Json数据 */ public static JSONArray readExcel(String path, Map<String, String> myMap) throws Exception { File file = new File(path); int res = checkFile(file); if (res == 0) { System.out.println("File not found"); } else if (res == 1) { return readXLSX(file, myMap); } else if (res == 2) { return readXLS(file, myMap); } JSONArray array = new JSONArray(); return array; } /** * 判断File文件的类型 * * @param file 传入的文件 * @return 0-文件为空,1-XLSX文件,2-XLS文件,3-其他文件 */ public static int checkFile(File file) { if (file == null) { return 0; } String fileName = file.getName(); if (fileName.endsWith(XLSX)) { return 1; } if (fileName.endsWith(XLS)) { return 2; } return 3; } /** * 读取XLSX文件 * * @param file * @return * @throws IOException * @throws InvalidFormatException */ public static JSONArray readXLSX(File file, Map<String, String> myMap) throws IOException, InvalidFormatException { Workbook book = new XSSFWorkbook(file); //只读第一个sheet表内容 Sheet sheet = book.getSheetAt(0); return read(sheet, book, myMap); } /** * 读取XLS文件 * * @param file 文件对象 * @throws IOException * @throws FileNotFoundException */ public static JSONArray readXLS(File file, Map<String, String> myMap) throws FileNotFoundException, IOException { POIFSFileSystem poifsFileSystem = new POIFSFileSystem(new FileInputStream(file)); Workbook book = new HSSFWorkbook(poifsFileSystem); //只读第一个sheet表内容 Sheet sheet = book.getSheetAt(0); return read(sheet, book, myMap); } /** * 解析数据 * * @param sheet 表格sheet对象 * @param book 用于流关闭 * @param myMap excel列名替换的值 * @return * @throws IOException */ public static JSONArray read(Sheet sheet, Workbook book, Map<String, String> myMap) throws IOException { int rowStart = sheet.getFirstRowNum(); // 首行下标 int rowEnd = sheet.getLastRowNum(); // 尾行下标 // 如果首行与尾行相同,表明只有一行,直接返回空数组 if (rowStart == rowEnd) { book.close(); return new JSONArray(); } // 获取第一行JSON对象键 Row firstRow = sheet.getRow(rowStart); int cellStart = firstRow.getFirstCellNum(); int cellEnd = firstRow.getLastCellNum(); Map<Integer, String> keyMap = new HashMap<Integer, String>(); for (int j = cellStart; j < cellEnd; j++) { keyMap.put(j, getValue(firstRow.getCell(j), rowStart, j, book, true)); } keyMap = replaceKey(keyMap, myMap); // 获取每行JSON对象的值 JSONArray array = new JSONArray(); for (int i = rowStart + 1; i <= rowEnd; i++) { Row eachRow = sheet.getRow(i); JSONObject obj = new JSONObject(); StringBuffer sb = new StringBuffer(); for (int k = cellStart; k < cellEnd; k++) { if (eachRow != null) { String val = getValue(eachRow.getCell(k), i, k, book, false); sb.append(val); // 所有数据添加到里面,用于判断该行是否为空 obj.put(keyMap.get(k), val); } } if (sb.toString().length() > 0) { array.add(obj); } } book.close(); return array; } /** * 获取每个单元格的数据 * * @param cell 单元格对象 * @param rowNum 第几行 * @param index 该行第几个 * @param book 主要用于关闭流 * @param isKey 是否为键:true-是,false-不是。 如果解析Json键,值为空时报错;如果不是Json键,值为空不报错 * @return * @throws IOException */ public static String getValue(Cell cell, int rowNum, int index, Workbook book, boolean isKey) throws IOException { // 空白或空 if (cell == null || cell.getCellType() == Cell.CELL_TYPE_BLANK) { if (isKey) { book.close(); throw new NullPointerException(String.format("the key on row %s index %s is null ", ++rowNum, ++index)); } else { return ""; } } // 0. 数字 类型 if (cell.getCellType() == Cell.CELL_TYPE_NUMERIC) { if (HSSFDateUtil.isCellDateFormatted(cell)) { Date date = cell.getDateCellValue(); DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return df.format(date); } //防止当作数字而导致最后的0丢失 DecimalFormat df = new DecimalFormat("0"); String val = df.format(cell.getNumericCellValue()); val = val.toUpperCase(); if (val.contains("E")) { val = val.split("E")[0].replace(".", ""); } return val; } // 1. String类型 if (cell.getCellType() == Cell.CELL_TYPE_STRING) { String val = cell.getStringCellValue(); if (val == null || val.trim().length() == 0) { if (book != null) { book.close(); } return ""; } return val.trim(); } // 2. 公式 CELL_TYPE_FORMULA if (cell.getCellType() == Cell.CELL_TYPE_FORMULA) { return cell.getStringCellValue(); } // 4. 布尔值 CELL_TYPE_BOOLEAN if (cell.getCellType() == Cell.CELL_TYPE_BOOLEAN) { return cell.getBooleanCellValue() + ""; } // 5. 错误 CELL_TYPE_ERROR return ""; } /** * 替换读取的数据key * * @param oldMap 原始map * @param myMap 要替换的value map * @return */ public static Map<Integer, String> replaceKey(Map<Integer, String> oldMap, Map<String, String> myMap) { Map<Integer, String> newMap = new HashMap<>(); int size = myMap.size(); if (oldMap.size() != size) { throw new NullPointerException("表格内容无法识别,请检查内容的规范性!"); } else { for (Integer key : oldMap.keySet()) { for (String key2 : myMap.keySet()) { if (oldMap.get(key).equals(key2)) { newMap.put(key, myMap.get(key2)); } } } } return newMap; } public static void exportExcel(String sheetName, Map<String, String> keyMap, String fileName, List<Map> list, HttpServletResponse response) throws Exception { } /** * 使用浏览器选下载 * * @param response * @param fileName 下载时的文件名 * @param data * @throws Exception */ public static void exportExcel(HttpServletResponse response, String fileName, ExcelData data) throws Exception { // 告诉浏览器用什么软件可以打开此文件 response.setHeader("content-Type", "application/vnd.ms-excel"); // 下载文件的默认名称 response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName + ".xls", "utf-8")); exportExcel(data, response.getOutputStream()); } /** * 本地下载 * * @param excelData * @param path 文件要存储的路径 * @return * @throws Exception */ public static int generateExcel(ExcelData excelData, String path) throws Exception { File f = new File(path); FileOutputStream out = new FileOutputStream(f); return exportExcel(excelData, out); } /** * 创建excel表 * * @param data * @param out * @return * @throws Exception */ private static int exportExcel(ExcelData data, OutputStream out) throws Exception { XSSFWorkbook wb = new XSSFWorkbook(); int rowIndex = 0; try { //设置工作表的名字 String sheetName = data.getName(); if (null == sheetName) { sheetName = "Sheet1"; } //创建工作表 XSSFSheet sheet = wb.createSheet(sheetName); rowIndex = writeExcel(wb, sheet, data); wb.write(out); } catch (Exception e) { e.printStackTrace(); } finally { //此处需要关闭 wb 变量 out.close(); } return rowIndex; } /** * 设置表显示字段 * * @param wb * @param sheet * @param data * @return */ private static int writeExcel(XSSFWorkbook wb, Sheet sheet, ExcelData data) { int rowIndex = 0; rowIndex = writeTitlesToExcel(wb, sheet, data.getTitles()); rowIndex = writeRowsToExcel(wb, sheet, data.getRows(), rowIndex); autoSizeColumns(sheet, data.getTitles().size() + 1); return rowIndex; } /** * 设置表头 * * @param wb * @param sheet * @param titles * @return */ private static int writeTitlesToExcel(XSSFWorkbook wb, Sheet sheet, List<String> titles) { int rowIndex = 0; int colIndex = 0; Font titleFont = wb.createFont(); //设置字体 titleFont.setFontName("宋体"); //设置字号 titleFont.setFontHeightInPoints((short) 12); //设置颜色 titleFont.setColor(IndexedColors.BLACK.index); XSSFCellStyle titleStyle = wb.createCellStyle(); titleStyle.setFont(titleFont); setBorder(titleStyle, BorderStyle.THIN); Row titleRow = sheet.createRow(rowIndex); titleRow.setHeightInPoints(25); colIndex = 0; for (String field : titles) { Cell cell = titleRow.createCell(colIndex); cell.setCellValue(field); cell.setCellStyle(titleStyle); colIndex++; } rowIndex++; return rowIndex; } /** * 设置内容 * * @param wb * @param sheet * @param rows * @param rowIndex * @return */ private static int writeRowsToExcel(XSSFWorkbook wb, Sheet sheet, List<List<Object>> rows, int rowIndex) { int colIndex; Font dataFont = wb.createFont(); dataFont.setFontName("宋体"); dataFont.setFontHeightInPoints((short) 12); dataFont.setColor(IndexedColors.BLACK.index); XSSFCellStyle dataStyle = wb.createCellStyle(); dataStyle.setFont(dataFont); setBorder(dataStyle, BorderStyle.THIN); for (List<Object> rowData : rows) { Row dataRow = sheet.createRow(rowIndex); dataRow.setHeightInPoints(25); colIndex = 0; for (Object cellData : rowData) { Cell cell = dataRow.createCell(colIndex); if (cellData != null) { cell.setCellValue(cellData.toString()); } else { cell.setCellValue(""); } cell.setCellStyle(dataStyle); colIndex++; } rowIndex++; } return rowIndex; } /** * 自动调整列宽 * * @param sheet * @param columnNumber */ private static void autoSizeColumns(Sheet sheet, int columnNumber) { for (int i = 0; i < columnNumber; i++) { int orgWidth = sheet.getColumnWidth(i); sheet.autoSizeColumn(i, true); int newWidth = (int) (sheet.getColumnWidth(i) + 100); if (newWidth > orgWidth) { sheet.setColumnWidth(i, newWidth); } else { sheet.setColumnWidth(i, orgWidth); } } } /** * 设置边框 * * @param style * @param border */ private static void setBorder(XSSFCellStyle style, BorderStyle border) { style.setBorderTop(border); style.setBorderLeft(border); style.setBorderRight(border); style.setBorderBottom(border); } }
此工具类是用来解析excel内容以及导出excel。
3.2实战演练
1)编写导入接口
@PostMapping("/import") public JSONObject importExcel(MultipartFile file) { JSONObject jsonObject = new JSONObject(); try { //获取配置文件的内容 Map<String, String> keyMap = PropertiesUtil.getKeyAndValue(importConfigName, "student."); //上传文件 String path = FileUtil.uploadFile(file, uploadPath); //读取上传的文件内容 JSONArray jsonArray = ExcelUtil.readExcel(path, keyMap); int count = studentService.importExcel(jsonArray); if (count >= 0) { jsonObject.put("status", true); jsonObject.put("msg", "学生信息导入成功"); } else { jsonObject.put("status", false); jsonObject.put("msg", "学生信息导入失败"); } } catch (Exception e) { log.info("=====学生信息导入发生异常=====" + e.getMessage()); jsonObject.put("status", false); jsonObject.put("msg", "学生信息导入失败"); } return jsonObject; }
2)编写导入的实体类、service及dao(见源码)。
3)把资源目录下templates目录下的excel文件复制一份,加入相关的数据
4)在资源目录新建导入的配置文件importConfig.properties,内容如下
#文件的读取和导入相关基础配置 #student相关配置,名字必须叫student,下面同 student.name=姓名 student.sno=学号 student.sex=性别 student.phone=手机号
5)添加导入的页面import.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>导入导出excel</title> </head> <body> <div> <a href="/download2">下载模板</a> </div> <form action="/import" method="post" enctype="multipart/form-data"> <input type="file" name="file" value="选择文件"> <input type="submit" value="导入"> </form> </body> </html>
6)测试。访问http://localhost:8080/import.html,点击导入,选择编辑好的excel,然后导入,会返回导入成功的信息,查看数据库,数据已被导入进来了。
4.poi实现文件导出
1)添加导出的接口
@GetMapping("/export") public void exportExcel(HttpServletResponse response) throws Exception { List<Student> list = studentService.queryList(); ExcelData data = new ExcelData(); String fileName = "学生信息表"; //设置工作表名称 data.setName(fileName); //设置表头 List<String> titles = new ArrayList(); titles.add("姓名"); titles.add("学号"); titles.add("性别"); titles.add("手机号"); data.setTitles(titles); //设置数据内容 List<List<Object>> rows = new ArrayList(); for (Student stu : list) { List<Object> row = new ArrayList(); row.add(stu.getName()); row.add(stu.getSno()); row.add(stu.getSex()); row.add(stu.getPhone()); rows.add(row); } data.setRows(rows); try { String formatName = new SimpleDateFormat("HHmmss").format(new Date()); ExcelUtil.exportExcel(response, fileName + formatName, data); } catch (Exception e) { log.info("=====学生信息导出发生异常=====" + e.getMessage()); } }
2)在import页面添加导出的链接
<a href="/export">导出</a>
3)测试。访问http://localhost:8080/import.html,点击导出,即可导出相应的数据。