0%

Spring Boot2 实战系列之文件上传下载

前言

文件上传和下载是很多网站都会有的功能,这次我们来实践下用 Spring Boot 实现这个功能。为了方便演示,没有实现将文件存储到数据库中,只是用到了一个 List 将上传文件的相关信息保存在内存中,文件本身放在本地磁盘。

创建项目

项目结构图如下:

pom 依赖如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>top.yekongle</groupId>
<artifactId>springboot-fileupload-sample</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-fileupload-sample</name>
<description>file upload sample for Spring Boot</description>

<properties>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

代码编写

application.properties, 全局配置,文件上传配置根据自身需求进行调整。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Thymeleaf 配置
# 模板文件存放路径
spring.thymeleaf.prefix=classpath:/templates/
# 文件后缀
spring.thymeleaf.suffix=.html
# 使用 html5
spring.thymeleaf.mode=HTML5
# 编码
spring.thymeleaf.encoding=UTF-8
# 是否启动缓存
spring.thymeleaf.cache=false

# 文件上传配置
# 是否支持多文件上传
spring.servlet.multipart.enabled=true
# 上传文件目录
spring.servlet.multipart.location=F:/upload
# 最大上传文件大小,默认1MB
spring.servlet.multipart.max-file-size=5MB
# 最大请求大小, 默认10M
spring.servlet.multipart.max-request-size=50MB
#文件大小阈值,当大于这个阈值时将写入到磁盘,否则存在内存中(默认值0 一般情况下不用特意修改)
spring.servlet.multipart.file-size-threshold=0

FileInfo.java,实体类,代表文件信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package top.yekongle.fileupload.entity;

import lombok.AllArgsConstructor;
import lombok.Data;

/**
* @Author: Yekongle
* @Date: 2020年4月28日
*/

@Data
@AllArgsConstructor
public class FileInfo {
private String name;
private String contentType;
private long size;
private String uploadDate;
}

UploadController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package top.yekongle.fileupload.controller;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
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;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;


import lombok.extern.slf4j.Slf4j;
import top.yekongle.fileupload.entity.FileInfo;

/**
* @Description: 文件上传
* @Author: Yekongle
*/

@Controller
@Slf4j
public class UploadController {

@Value("${spring.servlet.multipart.location}")
private String path;

private static final int DEFAULT_MAX_FILE_SIZE = 5 * 1000 * 1000;
private static final int DEFAULT_REQUEST_MAX_SIZE = 50 * 1000 * 1000;

private SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private List<FileInfo> fileInfoList = new ArrayList<FileInfo>();

@GetMapping("/singleFileUpload")
public String getSingleFileUploadPage(Model model) {
model.addAttribute("files", fileInfoList);
return "single_file_upload";
}

@GetMapping("/multiFileUpload")
public String getMultiFileUploadPage(Model model) {
model.addAttribute("files", fileInfoList);
return "multi_file_upload";
}

@PostMapping("/singleFileUpload")
public String singleFileUpload(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes)
throws IOException {
File filePath = new File(path);
log.info("文件保存的路径为: {}", filePath.getAbsolutePath());
if (!filePath.exists() && !filePath.isDirectory()) {
// 该文件目录不存在则创建
filePath.mkdir();
}
// 判断文件是否为空
if (file.isEmpty()) {
redirectAttributes.addFlashAttribute("message", "文件为空!");
return "redirect:/singleFileUpload";
}
// 判断文件是否为空文件
if (file.getSize() <= 0) {
redirectAttributes.addFlashAttribute("message", "文件大小为空,上传失败!");
return "redirect:/singleFileUpload";
}
// 判断文件大小不能大于5MB
if (file.getSize() > DEFAULT_MAX_FILE_SIZE) {
redirectAttributes.addFlashAttribute("message", "上传的文件不能大于5M!");
return "redirect:/singleFileUpload";
}
// 获取文件名
String fileName = file.getOriginalFilename();

log.info("上传的文件名为:{}", fileName);
log.info("上传的文件大小为:{}", file.getSize());
log.info("上传的文件类型为:{}", file.getContentType());
// 在指定目录下创建该文件
File newfile = new File(path, fileName);
if (newfile.exists()) {
redirectAttributes.addFlashAttribute("message", "该文件已经存在!");
return "redirect:/singleFileUpload";
} else {
FileInfo fileInfo = new FileInfo(fileName, file.getContentType(), file.getSize(), sf.format(new Date()));
fileInfoList.add(fileInfo);
}

// 将文件保存到该目录
file.transferTo(newfile);
return "redirect:/singleFileUpload";
}

@PostMapping("/multiFileUpload")
public String multiFileUpload(@RequestParam("files") MultipartFile[] files, RedirectAttributes redirectAttributes)
throws IOException {
if (null == files) {
redirectAttributes.addFlashAttribute("message", "参数为空!");
return "redirect:/multiFileUpload";
}
File filePath = new File(path);
log.info("文件保存的路径为: {}", filePath.getAbsolutePath());
if (!filePath.exists() && !filePath.isDirectory()) {
// 该文件目录不存在则创建
filePath.mkdir();
}
for (MultipartFile uploadFile : files) {
if (uploadFile.isEmpty()) {
redirectAttributes.addFlashAttribute("message", "文件为空!");
return "redirect:/multiFileUpload";
}
// 判断文件是否为空文件
if (uploadFile.getSize() <= 0) {
redirectAttributes.addFlashAttribute("message", "文件大小为空,上传失败!");
return "redirect:/multiFileUpload";
}
// 判断文件大小不能大于50MB
if (uploadFile.getSize() > DEFAULT_REQUEST_MAX_SIZE) {
redirectAttributes.addFlashAttribute("message", "上传的文件不能大于5M!");
return "redirect:/multiFileUpload";
}
// 获取文件名
String fileName = uploadFile.getOriginalFilename();

log.info("上传的文件名为:{}", fileName);
log.info("上传的文件大小为:{}", uploadFile.getSize());
log.info("上传的文件类型为:{}", uploadFile.getContentType());
// 在指定目录下创建该文件
File newfile = new File(path, fileName);
if (!newfile.exists()) {
FileInfo fileInfo = new FileInfo(fileName, uploadFile.getContentType(), uploadFile.getSize(),
sf.format(new Date()));
fileInfoList.add(fileInfo);
}

// 将文件保存到该目录
uploadFile.transferTo(newfile);
}

return "redirect:/multiFileUpload";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package top.yekongle.fileupload.controller;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import lombok.extern.slf4j.Slf4j;

/**
* @Description: 文件下载
* @Author: Yekongle
*/

@Controller
@Slf4j
public class DownloadController {

private static final String FILE_STORE_PATH = "F:/upload";

@GetMapping("/downloadFile")
public String getUploadPage() {
return "file_download";
}

/**
* 文件下载
*
* @return
* @throws UnsupportedEncodingException
*/
@PostMapping("/downloadFile")
@ResponseBody
public String downLoadFile(HttpServletRequest request, HttpServletResponse response, Model model)
throws UnsupportedEncodingException {
log.info("要下载的文件:{}", request.getParameter("fileName"));
String fileName = request.getParameter("fileName");
log.info("File name:{}", fileName);

File file = new File(FILE_STORE_PATH, fileName);
log.info("File path:{}", file.getAbsolutePath());

if (file.exists()) {
// 配置文件下载
response.setHeader("content-type", "application/octet-stream");
response.setContentType("application/octet-stream");
// 下载文件能正常显示中文
response.setHeader("Content-Disposition",
"attachment;filename=" + new String(fileName.getBytes(), "utf-8"));
// 文件内容长度
response.setHeader("Content-Length", "" + file.length());
// 读取文件
BufferedInputStream bi = null;
try {
byte[] buffer = new byte[1024];
bi = new BufferedInputStream(new FileInputStream(file));
ServletOutputStream outputStream = response.getOutputStream();
int i = -1;
while (-1 != (i = bi.read(buffer))) {
outputStream.write(buffer, 0, i);
}
return "下载成功";
} catch (Exception e) {
e.printStackTrace();
return "下载失败";
} finally {
if (bi != null) {
try {
bi.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

return "文件不存在";
}

}

前端页面
single_file_upload.html, 只可以上传单个文件,上传成功会返回文件信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>单个文件上传</title>
<script src="../static/js/jquery-1.10.2.min.js"
th:src="@{/js/jquery-1.10.2.min.js}"></script>
</head>
<body>
<div th:if="${message}">
<h2 style="color: red;" th:text="${message}" />
</div>

<div>
<h2>单个文件上传</h2>
<form action="singleFileUpload" method="POST"
enctype="multipart/form-data">
<p>
文件:<input type="file" name="file" />
</p>
<p>
<input type="submit" value="上传" />
</p>
</form>
</div>

<div>
<table border="1">
<thead>
<tr>
<td>Name</td>
<td>contentType</td>
<td>size</td>
<td>uploadDate</td>
</tr>
</thead>
<tbody>
<tr th:if="${files.size()} eq 0">
<td colspan="4">没有文件信息!!</td>
</tr>
<tr th:each="file : ${files}">
<td th:text="${file.name}" /></td>
<td th:text="${file.contentType}"></td>
<td th:text="${file.size}"></td>
<td th:text="${file.uploadDate}"></td>
</tr>
</tbody>
</table>
</div>

</body>

</html>

multi_file_upload.html, 可以多个文件一起上传,同样会返回文件信息列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>多个文件上传</title>
<script src="../static/js/jquery-1.10.2.min.js"
th:src="@{/js/jquery-1.10.2.min.js}"></script>
</head>
<body>
<div th:if="${message}">
<h2 style="color: red;" th:text="${message}" />
</div>

<div>
<h2>多文件上传</h2>
<form action="/multiFileUpload" method="POST"
enctype="multipart/form-data">
<p>
多文件上传:<input type="file" name="files" multiple="multiple" />
</p>
<p>
<input type="submit" value="上传" />
</p>
</form>
</div>

<div>
<table border="1">
<thead>
<tr>
<td>Name</td>
<td>contentType</td>
<td>size</td>
<td>uploadDate</td>
</tr>
</thead>
<tbody>
<tr th:if="${files.size()} eq 0">
<td colspan="4">没有文件信息!</td>
</tr>
<tr th:each="file : ${files}">
<td th:text="${file.name}" /></td>
<td th:text="${file.contentType}"></td>
<td th:text="${file.size}"></td>
<td th:text="${file.uploadDate}"></td>
</tr>
</tbody>
</table>
</div>
</body>
</body>
</html>

file_download.html, 输入上传的文件名即可下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>文件下载</title>
<script src="../static/js/jquery-1.10.2.min.js"
th:src="@{/js/jquery-1.10.2.min.js}"></script>
</head>
<body>
<div>
<h2>文件下载</h2>

<form action="downloadFile" method="POST" enctype="multipart/form-data">
<p>
文件名:<input type="text" name="fileName" />
</p>
<p>
<input type="submit" value="下载文件" />
</p>
</form>
</div>
</body>
</html>

运行演示

启动项目

单个文件上传,访问 http://localhost:8080/singleFileUpload

如果上传文件不符合条件会报错

多个文件上传,访问 http://localhost:8080/multiFileUpload

下载上传的文件,访问 http://localhost:8080/downloadFile

项目已上传至 Github: https://github.com/yekongle/springboot-code-samples/tree/master/springboot-fileupload-sample , 希望对小伙伴们有帮助哦。

坚持原创技术分享,您的支持将鼓励我继续创作!