SpringMvc-文件上传

文件上传我一直感觉是个麻烦事,一提到文件上传我脑海中浮现的是二进制、byte数组、输入输出流、读写文件
感觉头都大了,所以为了以后不再头大,正好最近在看Spring in Action 正好研究下,用各种姿势上传文件。传各种小黄片^-^。

SpringMVC--小小牛博客

1.Spring MVC中配置文件上传

下面是我随手上传一张图片的请求体:

1
2
3
4
5
6
------WebKitFormBoundaryiYXA5UiOJRgWnKnt
Content-Disposition: form-data; name="upfile"; filename="0.jpg"
Content-Type: image/jpeg


------WebKitFormBoundaryiYXA5UiOJRgWnKnt--

DispatcherServlet并没有实现任何解析multipart请求数据的功能。它将该任务委托给了Spring中MultipartResolver策略接口的实现,通过这个实现类来解析multipart请求中的内容。从Spring 3.1开始,Spring内置了两个MultipartResolver的实现供我们选择:

  • CommonsMultipartResolver:使用Jakarta CommonsFileUpload解析multipart请求;
  • StandardServletMultipartResolver:依赖于Servlet 3.0对multipart请求的支持(始于Spring 3.1)。

一般优选StandardServletMultipartResolver,不过为什么我更喜欢StandardServletMultipartResolver呢?

使用Servlet 3.0解析multipart请求(StandardServletMultipartResolver)

在Spring应用上下文中,将其声明为bean就会非常简单,如下所示:

1
2
3
4
@Bean
public MultipartResolver multipartResolver() throws IOException{
return new StandardServletMultipartResolver();
}

上面配置还不能正常使用,我们还需要设置存放路径,限制文件大小、类型等
我们需在web.xml或Servlet初始化类中配置。
如果我们采用Servlet初始化类的方式来配置DispatcherServlet的话,这个初始化类应该已经实现了WebApplicationInitializer,那我们可以在Servlet registration上调用setMultipartConfig()方法,传入一
个MultipartConfigElement实例,

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
protected void customizeRegistration(Dynamic registration){
registration.setMultipartConfig (
new MultipartConfigElement(
"/tem/uploads",//路径
2097152, //上传文件的最大容量(以字节为单位)。默认是没有限制的。
4194304, //整个multipart请求的最大容量(以字节为单位),默认是没有限制的
0 //在上传的过程中,如果文件大小达到了一个指定最大容量(以字
//节为单位),将会写入到临时文件路径中。默认值为0,也就是
//所有上传的文件都会写入到磁盘上
)
);
}

配置类参考SpringMVC起步-构建Web应用程序

如果使用传统的xml配置,则在web.xml中配置如下:
SpringMVC--小小牛博客

使用Jakarta Commons FileUpload multipart解析器

Spring内置了CommonsMultipartResolver,可以作为StandardServletMultipartResolver的替代方案

将CommonsMultipartResolver声明为Spring bean的最简单方式如下:
SpringMVC--小小牛博客

2.处理multipart 请求(接收上传文件)

  • 表单上设置enctype=multipart/form-data,也就是说Content-Type:multipart/form-data;
  • 控制器接收参数上添加@RequestPart注解
    SpringMVC--小小牛博客

这种用数组接收的方式很原始,没用文件类型,文件名等等,还要自己写流存文件。

接收MultipartFile

将数组换成MultipartFile接收,内置方法如下:
SpringMVC--小小牛博客

将MultipartFile保存到七牛云,阿里云等云存储中

也就是利用MultipartFile获取文件信息保存到远程服务器,暂不写

Part接口:Spring MultipartFile的替代方案

和上面的API差不多,值得一提的是,如果在编写控制器方法的时候,通过Part参数的形式
接受文件上传,那么就没有必要配置MultipartResolver了。只有使用MultipartFile的时候,我们才需要
MultipartResolver。
SpringMVC--小小牛博客

3.上传案列

下面是我在项目中随手写的一个上传文件类,本来项目使用的是百度的Ueditor上传文件,但是Ueditor是上传文件到项目的目录下面,每次部署war包都会覆盖原来的上传文件,必须先备份上传的文件,部署完项目后又重新传上去,很是麻烦。由于是前后端分离,前端静态资源部署在Nginx所以自己写了个上传请求将文件传到Nginx目录下边。

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
@Controller
@Api(tags="文件上传")
@RequestMapping("/file")
public class FileUploadController {
/**文件存放主路径*/
@Value("${cnct.upload.file.path}")
private String baseFilePath;
/**系统路径分隔符*/
public static final String FILE_SEPARATOR = System.getProperty("file.separator");
/**文件访问前缀,一般为Nginx路径*/
@Value("${cnct.request.file.path}")
private String requestFilePath;

/**
* 多文件上传
* @param request
* @return
*/
@RequestMapping("/multipleFileUpload")
@ResponseBody
public List<Map<String, Object>> multipleFileUpload(HttpServletRequest request){
List<Map<String,Object>> res = new ArrayList<>();
List<MultipartFile> files = ((MultipartHttpServletRequest) request).getFiles("file");
if(files == null || files.size() == 0){
return null;
}
for(MultipartFile file : files){//遍历存文件
Map<String,Object> map = new HashMap<>();
try {
// 截取上传文件的文件名
String uploadFilePath = file.getOriginalFilename();
String fileName = uploadFilePath
.substring(uploadFilePath.lastIndexOf('\\') + 1,
uploadFilePath.indexOf('.'));
// 截取上传文件的后缀
String suffixName = uploadFilePath.substring(
uploadFilePath.indexOf('.'), uploadFilePath.length());
String filePath = baseFilePath + new DateTime().getYear()+FILE_SEPARATOR +new DateTime().getMonthOfYear()+FILE_SEPARATOR +new DateTime().getDayOfMonth();
String realFileName = String.valueOf(new DateTime().getMillis()) + suffixName;
File f = new File(filePath+FILE_SEPARATOR+realFileName);

if(!f.getParentFile().exists()){
f.getParentFile().mkdirs();
}
f.createNewFile();
file.transferTo(f);
map.put("original",fileName+suffixName);
map.put("size",file.getSize());
map.put("state","SUCCESS");
map.put("title",realFileName);
map.put("type",suffixName);
map.put("url",requestFilePath+new DateTime().getYear()+
"/" +new DateTime().getMonthOfYear()+
"/" +new DateTime().getDayOfMonth()+
"/" +realFileName);
} catch (IOException e) {
System.err.println("文件不存在或者文件无权限");
map.put("state","ERROR");
e.printStackTrace();
}
res.add(map);
}
return res;
}
/**
* 单文件上传
* @param file
* @return
*/
@RequestMapping("/singleFileUpload")
@ResponseBody
public Map<String , Object> singleFileUpload(MultipartFile file){
Map<String,Object> res = new HashMap<>();
try {
if (file.isEmpty()) {
res.put("state","ERROR");
return res;
}
// 截取上传文件的文件名
String uploadFilePath = file.getOriginalFilename();
String fileName = uploadFilePath
.substring(uploadFilePath.lastIndexOf('\\') + 1,
uploadFilePath.indexOf('.'));
// 截取上传文件的后缀
String suffixName = uploadFilePath.substring(
uploadFilePath.indexOf('.'), uploadFilePath.length());
String filePath = baseFilePath + new DateTime().getYear()+FILE_SEPARATOR +new DateTime().getMonthOfYear()+FILE_SEPARATOR +new DateTime().getDayOfMonth();
String realFileName = String.valueOf(new DateTime().getMillis()) + suffixName;
File f = new File(filePath+FILE_SEPARATOR+realFileName);

if(!f.getParentFile().exists()){
f.getParentFile().mkdirs();
}
f.createNewFile();
file.transferTo(f);
res.put("original",fileName+suffixName);
res.put("size",file.getSize());
res.put("state","SUCCESS");
res.put("title",realFileName);
res.put("type",suffixName);
res.put("url",requestFilePath+new DateTime().getYear()+
"/" +new DateTime().getMonthOfYear()+
"/" +new DateTime().getDayOfMonth()+
"/" +realFileName);
} catch (IOException e) {
System.err.println("文件不存在或者文件无权限");
res.put("state","ERROR");
e.printStackTrace();
}
return res;
}
}

项目并发量不大,所以代码没过多优化设计。项目使用的是SpringBoot
spring:
http:
multipart:
max-file-size: 100MB
max-request-size: 100MB
enabled: true
SpringBoot大发好,multipart都不需要配那么多那么麻烦,真是浪费生命