第十四节 SpringBoot上传文件
一、使用上传技术
(1)在yml中添加上传配置。限制单个文件上传以及多个文件总大小限制。如果超出限制,页面将出现报错页面。
spring:
#上传文件使用
servlet:
multipart:
#单个文件最大上传大小
max-file-size: 10MB
#每次请求上传文件大小最大值
max-request-size: 30MB
(2)添加自定义参数。实际项目中一般部署在Linux,需要指明路径。我这里配置的是Windows的路径。
#自定义参数
define:
nginx:
path: D:\uploadFile\
(3)编写一个Controller,用于跳转与上传。指明跳转到templates文件夹下的upload.html页面。
@GetMapping(value = "/goToUpload")
public String goToUploadHtml() {
LOGGER.info("Go To upload.html");
return "upload";
}
(4)编写upload.html页面。页面上传路径为upload与 uploadFiles,用于 单个上传与批量上传。
<!DOCTYPE html>
<html>
<head>
<title>SpringBoot上传文件</title>
<meta name="keywords" content="keyword1,keyword2,keyword3"/>
<meta name="description" content="this is my page"/>
<meta name="content-type" content="text/html; charset=UTF-8"/>
</head>
<body>
<form enctype="multipart/form-data" method="post" action="upload">
图片:<input type="file" name="file"/>
<input type="submit" value="上传"/>
</form>
<br>
<br>
<br>
<br>
<br>
<form enctype="multipart/form-data" method="post" action="uploadFiles">
图片:<input type="file" name="file"/><br>
图片:<input type="file" name="file"/><br>
图片:<input type="file" name="file"/><br>
<input type="submit" value="上传全部"/>
</form>
</body>
</html>
(5)在Controller中编写处理逻辑。考虑到多文件上传失败的情况,因此需要把上传,使用事务进行管理。
package com.zhoutianyu.learnspringboot.upload;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
@Controller
public class UploadController {
private static final Logger LOGGER = LoggerFactory.getLogger(UploadController.class);
private static final String NULL_FILE = "";
@Value("${define.nginx.path}")
private String nginxPath;
@Autowired
private FileService fileService;
@GetMapping(value = "/goToUpload")
public String goToUploadHtml() {
LOGGER.info("Go To upload.html");
return "upload";
}
@PostMapping("/upload")
@ResponseBody
public String singleFileUpload(@RequestParam("file") MultipartFile file) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("fileName = {}", file.getOriginalFilename());
}
try {
if (file == null || NULL_FILE.equals(file.getOriginalFilename())) {
return "upload failure";
}
fileService.saveFile(file.getBytes(), nginxPath, file.getOriginalFilename());
} catch (Exception e) {
return "upload failure";
}
return "upload success";
}
@PostMapping("/uploadFiles")
@ResponseBody
public String multiFileUpload(@RequestParam("file") MultipartFile[] files) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("fileName = {}", files[0].getOriginalFilename());
}
try {
for (int i = 0; i < files.length; i++) {
//check file
if (NULL_FILE.equals(files[i].getOriginalFilename())) {
continue;
}
fileService.saveFile(files[i].getBytes(), nginxPath, files[i].getOriginalFilename());
}
} catch (Exception e) {
return "upload failure";
}
return "upload success";
}
}
package com.zhoutianyu.learnspringboot.upload;
public interface FileService {
void saveFile(byte[] file, String filePath, String fileName) throws Exception;
}
package com.zhoutianyu.learnspringboot.upload;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.File;
import java.io.FileOutputStream;
@Service
@Transactional
public class FileServiceImpl implements FileService {
@Override
public void saveFile(byte[] file, String filePath, String fileName) throws Exception {
File targetFile = new File(filePath);
if (!targetFile.exists()) {
targetFile.mkdirs();
}
FileOutputStream out = new FileOutputStream(filePath + fileName);
out.write(file);
out.flush();
out.close();
}
}
二、测试
分别测试:
(1)单个文件上传
(2)多文件上传
(3)上传单文件超过10M(配置文件中要求不能上传超过10M文件)。期望上传失败。
三、下载文件
下载文件的代码就更加简单了。
拨云见日
在分布式集群环境中,传统的上传文件都是存储在项目的某个文件夹下,比如WEB-INF下面,不对用户暴露。但是在分布式部署的情况下,A服务器上传的文件只能上传到A服务器下载,如果请求被负载均衡转发到B服务器,那么B服务器就下载不到文件。我在实际的工作中解决方案有三种。
① 由于分布式系统共享同一个数据库服务器。那么就可以把文件服务器搭建在与数据库同一台机器上。下面的代码中,nginxPath变量就是文件服务器的用于存放文件的文件夹路径。适合私有云与公有云部署。
② 把上传的文件直接存放到数据库中,但是这种解决方案被研发经理批评,原因是在数据库迁移会相当麻烦,而且如果有大文件的上传与下载,会占用过多的数据库资源,阻塞整个系统。此方案强烈反对在实际工作中使用,虽然曾经用过。
③ 购买云文件服务器。例如OSS文件服务器系统,把所有文件上传到公共得文件服务器中,这样就能让多个节点共享资源。缺点也有,如果客户的服务器不能支持访问公网的话,那么此方案会行不通,需要提前与客户确定运行环境。
@Override
public void download(HttpServletResponse response, String filename) {
try {
// 文件地址,真实环境是存放在文件服务器的
File file = new File(nginxPath + filename);
filename = new String((filename).getBytes(), UTF_8);
// 输入对象
FileInputStream fis = new FileInputStream(file);
// 设置相关格式
response.setContentType("application/force-download");
response.setCharacterEncoding(UTF_8);
// 设置下载后的文件名以及header
response.addHeader("Content-disposition",
"attachment;fileName=" + URLEncoder.encode(filename, UTF_8));
// 创建输出对象
OutputStream os = response.getOutputStream();
// 常规操作
byte[] buf = new byte[1024];
int len = 0;
while ((len = fis.read(buf)) != -1) {
os.write(buf, 0, len);
}
fis.close();
} catch (Exception e) {
throw new RuntimeException("下载异常异常", e.fillInStackTrace());
}
}
四、源码下载
本章节项目源码:点我下载源代码