第二篇-进阶-第十四章-上传与下载
Java有很多种上传文件的实现方案,比如使用@MultipartConfig注解、Apache提供的commonupload组件等,SpringBoot采用的更为简单的方案(即org.springframework.web.multipart.MultipartFile接口)。
Java下载文件的实现方式也有很多种,比如静态的URL地址(不推荐)、WebSocket输出流等,SpringBoot通过HttpServletResponse输出流实现下载文件的功能。
14.1上传文件
当上传一个文件时,SpringBoot使用org.springframework.web.multipart.MultipartFile接口,当同时传多个文件,springBoot使用org.springframework.web.multipart.MultipartRequest接口予以实现。
14.1.1只上传一个文件
SpringBoot通过MultipartFile接口实现接收前端文件的功能,MultipartFile接口提供的方法及说明:
返回值 | 方法 | 说明 |
byte[] | getBytes() | 返回文件的字节数组形式 |
String | getContentType() | 返回文件的内容类型 |
InputStream | getInputStream() | 获取文件的字节输入流 |
String | getName() | 获取文件所用的参数名 |
String | getOriginalFilename() | 获取上传文件的原始文件名 |
long | getSize() | 返回文件包含的字节数 |
boolean | isEmpty() | 上传的文件是否为空 |
void | transferTo(File dest) | 将接收到的文件转移到dest文件 |
void | transferTo(Path dest) | 将接收的文件转移到dest路径,dest为NIO中的Path接口 |
服务器如果想要接收前端文件,就需要为控制器方法添加一个MultipartFile类型的参数,该参数会自动获取前端传来的数据,调用MultipartFile接口的transferTo()方法就能将上传文件的数据转移到服务器的本地文件中。
@RestController
public class UploadFileController {private final Logger logger = LoggerFactory.getLogger(UploadFileController.class.getName());@RequestMapping("/file")public String uploadFile(@RequestParam("file") MultipartFile file) {File fileOndesk = new File("E:\\upload\\");if (!fileOndesk.exists()) {logger.info("创建存储文件夹");fileOndesk.mkdirs();}try {String fileName = file.getOriginalFilename();logger.info("保存文件");file.transferTo(new File(fileOndesk, fileName));return "success";} catch (Exception e) {System.out.println("上传出错" + e.getMessage());return "wrong";}}
}
程序运行后使用Postman进行测试
最终发现文件保存到专门的文件夹中
如果上传文件过大,服务器可能会拒绝上传文件的请求,这就可以手动配置服务器允许上传文件的容量上限。(实测上传1个G的文件,报500的服务器错误异常)
application.properties#开启multipart上传功能(设置体积单位为MB)
spring.servlet.multipart.enabled=true
#最大文件大小
spring.servlet.multipart.max-file-size=10MB
#最大请求大小
spring.servlet.multipart.max-request-size=215MB
14.1.2 同时上传多个文件
SpringBoot通过org.springframework.web.multipart.MultipartRequest接口提供的方法实现获取批量文件的功能。
MultipartRequest接口提供的方法如下:
返回值 | 方法 | 说明 |
MultipartFile | getFile(String name) | 获取指定参数名的上传文件对象,如果不存在此参数,则返回为null |
Map | getFileMap() | 返回请求中上传文件的参数名的键值对,键为参数名,值为文件对象 |
Iterator | getFileName() | 返回请求中上传文件对应的参数名的迭代器 |
List | getFiles(String name) | 返回此请求中上传文件的内容和说明,不存在则返回空列表 |
MultiValueMap | getMultiFileMap() | 功能同getFileMap(),返回类型为MultiValueMap |
String | getMultipartContentType() | 返回上传文件的内容类型,图片文件可能为image/jpeg |
服务器如果想要批量获取这些文件,就需要在控制器方法中添加HttpServletRequest参数,再将该参数强转为MultipartHttpServletRequest类型,而后调用getFiles()方法。
具体实现如下:
@RestController
public class UploadFileController {private final Logger logger = LoggerFactory.getLogger(UploadFileController.class.getName());@RequestMapping("/files")public String uploadFiles(HttpServletRequest request) throws IOException {MultipartHttpServletRequest mrequest = (MultipartHttpServletRequest) request;List<MultipartFile> fileList = mrequest.getFiles("files");//根据请求参数获取文件列表logger.info("上传文件个数=" + fileList.size());File dir = new File("E:\\upload");for (MultipartFile file : fileList) {if (file.isEmpty()) {continue;}logger.info("上传文件大小=" + file.getSize());String fileName = file.getOriginalFilename();file.transferTo(new File(dir, fileName));}return "success";}}
程序运行后使用Postman进行测试
最终文件成功上传到指定文件夹下。
14.2下载文件
在实际开发中,SpringBoot通过HttpServletResponse输出流实现下载文件的功能。为了获取HttpServletResponse的输出流,需要对服务器响应客户端请求的response对象进行如下设置:
response.setContentType("application/octet-stream");
response.addHeader("Content-Disposition","attachment;fileName=demo.png");
- 当使用HttpServletResponse传输文件时,必须将传输类型设定为"multipart/form-data"或者"application/octet-stream",这样客户端才能知道服务器返回的是一个文件,而不是一堆乱码。
- Content-Disposition表示下载文件的标识字段,attachment表示在服务器的响应中添加附件,fileName=demo.png标识下载的文件名是demo.png
- 在设置response对象后,通过response对象调用getOutputStream()方法即可获取HttpServletResponse输出流,使用此输出流就能够实现后端向前端传输文件的功能
具体实现如下:
@Controller
public class DownloadController {Logger logger = LoggerFactory.getLogger(DownloadController.class.getName());@RequestMapping("/download/{code}")public void download(@PathVariable String code, HttpServletResponse response) throws Exception {String path = "";if ("head".equals(code)) {path = "E:\\upload\\head.jpg";} else if ("op1".equals(code)) {path = "E:\\upload\\op1.png";} else if ("op2".equals(code)) {path = "E:\\upload\\op2.png";} else {return;}logger.info("path="+path);File file = new File(path);if (file == null || !file.exists() || file.length() == 0) {logger.info("文件有问题");return;}response.setContentType("application/octet-stream");response.addHeader("Content-Length", String.valueOf(file.length()));//对文件名中的中文进行编码,防止中文乱码String fileName = URLEncoder.encode(file.getName(), "UTF-8");logger.info("fileName=="+fileName);//让文件作为附件被下载response.addHeader("Content-Disposition", "attachment;fileName=" + fileName);OutputStream os = response.getOutputStream();FileInputStream fis = new FileInputStream(file);byte[] buffer = new byte[1024];int len = 0;while ((len = fis.read(buffer)) != -1) {os.write(buffer, 0, len);}fis.close();os.close();}}
运行可下载图片附件
14.3 上传Excel文件中的数据
Excel可以算是简单的关系型存储数据的形式,就可以实现批量化数据上传。SpringBoot推荐使用Apache POI库,简称POI。
14.3.1添加POI依赖
Excel有2种格式,xls和slsx,前者是2003及更早版本的格式,后者是2007及以后的格式,添加的依赖各不相同,分别如下
xls格式的依赖
<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>4.1.2</version>
</dependency>
xlsx格式的依赖(默认使用这个即可)
<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>4.1.2</version>
</dependency>
14.3.2 读取Excel文件中的数据
POI将一个Excel文件划分下图的几个部分。每一个部分都对应一个POI接口,其中,Workbook表示整个Excel文件,Sheet表示Excel文件中的分页,Row标识Excel文件中的一行,Cell表示Excel文件中某一行的一个具体的单元格。
这些POI接口都位于org.apache.poi.ss.usermodel包下,读取Excel文件的每一个单元格的数据都需要按照"workbook-->Sheet-->Row-->Cell"的顺序创建对应的POI接口对象。
- 创建Workbook对象,可以通过文件创建,也可以通过字节流来创建Workbook对象
//1,根据File对象获取Workboot对象
File file = new File(exclePath);
Workbook workbook = WorkbookFactory.create(file);//2,根据字节流创建Workboot对象
InputStream is = new FileInputStream(exclePath);
Workbook workbook1 = WorkbookFactory.create(is);
- 创建Workbook后才能根据位置获取Sheet对象
Sheet sheet = workbook.getSheetAt(i);
- 得到Sheet对象后才能得到Row对象
Row row = sheet.getRow(0);
- 得到Row对象后才能根据位置得到精准的Cell对象
Cell cell=row.getCell(i); cell.toString()就能得到具体每个格子内的内容
以学生表test.excel为例
读取excle表中数据,并通过请求返回,程序如下:
@RestController
@RequestMapping("excel")
public class ExcelController {Logger logger = LoggerFactory.getLogger(ExcelController.class.getName());@RequestMapping("/read")public ArrayList<Student> readExcel() throws Exception {String exclePath = "E:\\upload\\test.xlsx";//1,根据File对象获取Workboot对象File file = new File(exclePath);Workbook workbook = WorkbookFactory.create(file);//2,根据字节流创建Workboot对象InputStream is = new FileInputStream(exclePath);Workbook workbook1 = WorkbookFactory.create(is);if (workbook != null) {int sheetNum = workbook.getNumberOfSheets();//得到sheet个数logger.info("sheet个数=" + workbook.getNumberOfSheets());ArrayList<Student> allStudents = new ArrayList<>();for (int i = 0; i < sheetNum; i++) {Sheet sheet = workbook.getSheetAt(i);if (sheet != null) {int rowNum = sheet.getPhysicalNumberOfRows();//得到row个数for (int j = 0; j < rowNum; j++) {Row row = sheet.getRow(j);//得到行对象Rowif (row != null) {int cellNum = row.getPhysicalNumberOfCells();if (cellNum == 4) {Student student = new Student();student.setName(row.getCell(0).toString());//根据索引得到每个精准的单元,并获取内容student.setAge(row.getCell(1).toString());student.setAddress(row.getCell(2).toString());student.setInfo(row.getCell(3).toString());allStudents.add(student);}}}}}return allStudents;} else {logger.info("workbook==null");}return null;}class Student {String name;String age;String address;String info;public String getName() {return name;}public void setName(String name) {this.name = name;}public String getAge() {return age;}public void setAge(String age) {this.age = age;}public String getAddress() {return address;}public void setAddress(String address) {this.address = address;}public String getInfo() {return info;}public void setInfo(String info) {this.info = info;}}@RequestMapping("/index")public String index() {return "index";}
}
运行结果如下:
打完收工。