0%

文件上传

准备工作

对于文件上传,浏览器在上传的过程中是将文件以流的形式提交到服务器端的。一般选择Apache的开源工具Common-fileupload这个文件上传组件。Common-fileupload是依赖于common-io这个包的。Maven依赖如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>

<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.7</version>
</dependency>

注意事项:

  • 为保证服务器安全,上传文件应该放在外界无法直接访问的目录下,比如放于WEB-INF目录下。
  • 为了防止文件覆盖的现象发生,要为上传文件产生一个唯一的文件夹名。
    • 时间戳、uuid、md5、位运算算法
  • 要限制上传文件的最大值。
  • 可以限制上传文件的类型,在收到上传文件名时,判断后缀名是否合法。

需要用到的类

ServletFileUpload负责处理上传的文件数据,并将表单中每个输入封装成一个FileItem对象,在使用ServletFileUpload对象解析请求时需要DiskFileItemFactory对象。所以,我们需要再进行解析工作前构造好DiskFileItemFactory对象,通过ServletFileUpload对象的构造方法或者setFileItemFactory()方法设置ServletFileUpload对象的FileItemFactory属性。

FileItem

在HTML页面中,form表单中的input必须要有name。<input type="file" name="filename1">

表单如果包含一个文件上传输入项的话,这个表单的enctype属性就必须设置为multipart/form-data。浏览器表单的类型如果为multipart/form-data,在服务器端想获取数据就要通过流。

1
2
3
4
5
6
7
8
9
<%--通过表单上传文件
get:上传文件大小有限制
post:上传文件大小没有限制--%>
<form action="" enctype="multipart/form-data" method="post">
<p>上传用户:<input type="text" name="username"></p>
<p><input type="file" name="filename1"></p>
<p><input type="file" name="filename2"></p>
<p><input type="reset"><input type="submit"></p>
</form>

常用方法介绍:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//isFormField方法,用于判断FileItem类对象封装的数据 是一个普通文本表单,还是一个文件表单。前者返回true,后者返回false
boolean isFormField();

//getFieldName()方法,用于返回表单标签name属性的值
String getFieldName();

//getName()方法,用于获得文件上传字段中的文件名。(客户端上传文件时,选择的文件的 文件名)
String getName();



//getString()方法,用于将FileItem对象中保存的数据流内容以一个字符串返回
String getString();

//以流的形式返回上传文件的数据内容
InputStream getInputStream();

//通过isFormField来判断数据类型,再决定使用哪个方法获取数据(上边的getString与getInputStream)



//delete方法用来清空FileItem类对象中存放的主体内容(如果主体内容被保存在临时文件中,delete方法将删除该临时文件)
void delete();

ServletFileUpload

ServletFileUpload负责处理上传的文件数据,并将表单中每个输入项封装成一个FileItem对象。使用parseRequest(HttpServletRequest)方法可以将表单中每一个HTML标签提交的数据封装成一个FileItem对象,然后以List列表的形式返回。使用该方法处理上传文件简单易用。

DiskFileItemFactory

将请求消息实体中的每一个项目封装成单独的DiskFileItem (FileItem接口的实现) 对象的任务
由 org.apache.commons.fileupload.FileItemFactory 接口的默认实现
org.apache.commons.fileupload.disk.DiskFileItemFactory 来完成。当上传的文件项目比较小时,直接保存在内存中(速度比较快),比较大时,以临时文件的形式,保存在磁盘临时文件夹(虽然速度慢些,但是内存资源是有限的)。摘自这篇博客

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
总结:
必须设置好上传文件的最大阀值
final long MAX_SIZE = 10 * 1024 * 1024 * 1024;// 设置上传文件最大为 10G

必须设置文件上传服务器上的临时目录

// 文件上传参数配置
// 创建一个新的文件上传句柄
DiskFileItemFactory factory = new DiskFileItemFactory();
// 设置内存缓冲区,超过后写入临时文件
factory.setSizeThreshold(4096);
// 设置上传到服务器上文件的临时存放目录 -- 非常重要,防止存放到系统盘造成系统盘空间不足
factory.setRepository(new File("F:\\uploadFileTemp"));
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setHeaderEncoding("utf-8");
// 设置单个文件的最大上传值
upload.setSizeMax(MAX_SIZE); // 文件上传上限10G

上传成功后一定要删除临时目录的临时文件

fileItem.delete(); // 请务必调用,在文件上传结束后,删除临时目录的文件...

最好记录下文件从开始上传到上传结束的时间点,这个对今后文件上传时间的分析很有用

代码部分

本案例由三部分组成。

  • 上传文件页面。demo13.jsp。通过post请求将文件上传到服务器端。
  • 处理post请求,Servlet程序,类名FileServlet。处理前端页面发来的post请求。
  • 上传状态页面。upload_info.jsp。显示文件上传成功还是失败。

上传页面

通过表单上传文件。表单的enctype属性需要设置为multipart/form-data。使用post请求来上传文件,是因为post请求上传文件大小没有限制。处理post请求的Servlet程序在web.xml中注册的请求路径为/servlet/upload.do。该文件放在web应用程序的根目录下,文件名为demo13.jsp。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>上传文件</title>
</head>
<body>
<%--通过表单上传文件
get:上传文件大小有限制
post:上传文件大小没有限制--%>
<form action="${pageContext.request.contextPath}/servlet/upload.do" enctype="multipart/form-data" method="post">
<p>用户名:<input type="text" name="username"></p>
<p>头像:<input type="file" name="avatarimg"></p>
<p>背景图片:<input type="file" name="bgimg"></p>
<p><input type="reset"> | <input type="submit"></p>
</form>

</body>
</html>

在Form元素的语法中,EncType表明提交数据的格式 用 Enctype 属性指定将数据回发到服务器时浏览器使用的编码类型。 例如: application/x-www-form-urlencoded: 窗体数据被编码为名称/值对。这是标准的编码格式。 multipart/form-data: 窗体数据被编码为一条消息,页上的每个控件对应消息中的一个部分,这个一般文件上传时用。 text/plain: 窗体数据以纯文本形式进行编码,其中不含任何控件或格式字符。摘自这篇博客

Servlet程序

Servlet程序。处理前端页面发来的post请求。在web.xml中注册的请求路径为/servlet/upload.do。类FileServlet的整体结构如下:

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
package com.qsdbl.jspdemo;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.UUID;

public class FileServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//判断上传的文件是普通表单还是带文件的表单
if (!ServletFileUpload.isMultipartContent(req)){
return;//终止方法运行。说明这是一个普通的表单,直接返回。不处理客户端请求
}//如果通过了这个if,说明我们的表单是带文件的表单
try {
//保存上传文件的目录
String uploadPath = this.getServletContext().getRealPath("/WEB-INF/upload");
//缓存,临时文件
String tempPath = this.getServletContext().getRealPath("/WEB-INF/temp");


//1、创建DiskFileItemFactory对象,处理文件上传路径或者大小限制的
DiskFileItemFactory factory = getDiskFileItemFactory(tempPath);
//2、获取ServletFileUpload
ServletFileUpload upload = getServletFileUpload(factory);
//3、处理上传的文件
String msg = uploadParseRequest(upload,req,uploadPath);


//重定向
resp.sendRedirect(req.getContextPath()+"/upload_info.jsp?msg="+msg);
}catch (Exception e){
e.printStackTrace();
}
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
//1、创建DiskFileItemFactory对象,处理文件上传路径或者大小限制
private DiskFileItemFactory getDiskFileItemFactory(String tempPath){...}
//2、获取ServletFileUpload
private ServletFileUpload getServletFileUpload(DiskFileItemFactory factory){...}
//3、处理上传的文件
private String uploadParseRequest(ServletFileUpload upload,HttpServletRequest req,String uploadPath){...}
}

判断数据类型

首先判断客户端发送来的数据,是否是带文件的表单。

1
2
3
4
//判断上传的文件是普通表单还是带文件的表单
if (!ServletFileUpload.isMultipartContent(req)){
return;//终止方法运行。说明这是一个普通的表单,直接返回。不处理客户端请求
}//如果通过了这个if,说明我们的表单是带文件的表单

文件保存

路径问题

确定文件保存的路径(可以像这篇博客中的一样,放到一个类中。方便修改)

uploadPath,保存上传文件的目录。

  • String uploadPath = this.getServletContext().getRealPath(“/WEB-INF/upload”);

  • 创建上传文件的保存路径,建议在WEB-INF路径下,安全,用户无法直接访问上传的文件

  • getServletContext()获取上下文路径,再加载文件,即使更换了服务器也是可以正常访问到web应用程序内的文件的

tempPath,临时文件夹。

  • 当上传的文件项目比较小时,直接保存在内存中(速度比较快),比较大时,以临时文件的形式,保存在磁盘临时文件夹(虽然速度慢些,但是内存资源是有限的)。所以我们需要临时文件夹tempPath。
  • 在后边设置DiskFileItemFactory对象属性时使用到
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
try {

//保存上传文件的目录
String uploadPath = this.getServletContext().getRealPath("/WEB-INF/upload");
//缓存,临时文件
String tempPath = this.getServletContext().getRealPath("/WEB-INF/temp");

//1、创建DiskFileItemFactory对象,处理文件上传路径或者大小限制的
DiskFileItemFactory factory = getDiskFileItemFactory(tempPath);
//2、获取ServletFileUpload
ServletFileUpload upload = getServletFileUpload(factory);
//3、处理上传的文件
String msg = uploadParseRequest(upload,req,uploadPath);

//重定向
resp.sendRedirect(req.getContextPath()+"/upload_info.jsp?msg="+msg);
}catch (Exception e){
e.printStackTrace();
}

保存操作

处理上传的文件,一般都需要通过流来获取,我们可以使用request.getInputStream(),但是原生态的文件上传流获取,十分麻烦。建议使用Apache的文件上传组件来实现,common-fileupload,它需要依赖于common-io组件。点这里,复习一下要用到的类。

使用Apache组件处理上传的文件(保存到服务器上),为了便于理解,这里将它们封装成了三个方法。getDiskFileItemFactory、getServletFileUpload、uploadParseRequest。

factory

1、创建DiskFileItemFactory对象,设置文件临时上传路径、大小限制(关于为什么要设置这两个参数,可以点这里复习一下)。主要的代码是第三行,后边的扩展不写也可以。返回一个DiskFileItemFactory对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private DiskFileItemFactory getDiskFileItemFactory(String tempPath){
//1、创建DiskFileItemFactory对象,处理文件上传路径或者大小限制
DiskFileItemFactory factory = new DiskFileItemFactory();

//扩展:
//通过这个工厂设置一个缓冲区,当上传的文件大于这个缓冲区的时候,将它放到 临时文件 中(减少内存占用)
factory.setSizeThreshold(1024*1024);//缓存区大小为1M(单位为字节)(文件大于该大小将会生成一个缓存文件放到缓存目录下,传输完毕后记得要使用delete删掉)
File tempFile = new File(tempPath);
if (!tempFile.exists()){//判断目录是否存在
tempFile.mkdir();//创建这个目录
}
factory.setRepository(tempFile);//临时文件的保存目录,需要一个File对象
return factory;
}

mkdir()是创建子目录。mkdirs()是创建多级目录。

upload

2、获取ServletFileUpload对象。主要的代码是第三行,后边的扩展不写也可以。返回一个ServletFileUpload对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private ServletFileUpload getServletFileUpload(DiskFileItemFactory factory){
//2、获取ServletFileUpload
ServletFileUpload upload = new ServletFileUpload(factory);

//扩展:
//监听文件上传进度
upload.setProgressListener(new ProgressListener() {
@Override
//pBytesRead,已读取到的文件大小
//pContentLent,文件大小
public void update(long pBytesRead, long pContentLent, int i) {
// System.out.println("总大小:"+pContentLent+" 已经上传:"+pBytesRead);
System.out.println("上传进度:"+String.format("%.0f",(pBytesRead/1.0/pContentLent)*100)+"%");
}
});
//处理乱码问题
upload.setHeaderEncoding("utf-8");
//设置单个文件的最大值(大于下边设置的值,会抛出异常)
// upload.setFileSizeMax(1024*1024*10);//10M
//设置总共能够上传文件的大小
// upload.setSizeMax(1024*1024*10);//10M

return upload;
}
uploadParse

3、处理上传的文件(io操作)

ServletFileUpload对象的parseRequest方法,将表单中每一个HTML标签提交的数据封装成一个FileItem对象。再通过一个for循环遍历所有的数据。

在遍历时,通过FileItem对象的isFormField()方法判断具体的某个表单项是什么类型的数据。(普通类型、文件类型)。如果是普通类型,则使用getString方法获取其中的数据。如果是文件类型,则通过getInputStream方法获取其中的数据,保存到服务器上。

1
2
3
4
5
6
7
8
9
10
11
List<FileItem> fileItems = upload.parseRequest(req);
for (FileItem fileItem : fileItems) {
if (fileItem.isFormField()){//该表单项是普通类型
//getFieldName指的是前端表单 控件的name。getString,获取控件中输入的值
String name = fileItem.getFieldName();
String value = fileItem.getString("utf-8");//指定编码类型为utf-8,避免中文乱码
System.out.println(name+":"+value);
}else {//该表单项是file类型
...
}
}

该方法的整体结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private String uploadParseRequest(ServletFileUpload upload,HttpServletRequest req,String uploadPath){
String msg = "fail!!!";//若上传成功,则会替换掉该提示消息
try {
List<FileItem> fileItems = upload.parseRequest(req);//将表单中每一个HTML标签提交的数据封装成一个FileItem对象
for (FileItem fileItem : fileItems) {
if (fileItem.isFormField()){//该表单项是普通类型
//getFieldName指的是前端表单 控件的name。getString,获取控件中输入的值
String name = fileItem.getFieldName();
String value = fileItem.getString("utf-8");//指定编码类型为utf-8,避免中文乱码
System.out.println(name+":"+value);
}else {//该表单项是file类型
...
}
}
}catch (Exception e){
e.printStackTrace();
return msg+" The file is too large.";
//与方法getServletFileUpload中设置的限制文件大小有关
}
return msg;
}

处理文件类型的数据(上边else中省略的部分):

  • 处理文件
    • 文件名是否合法、类型是否符合等
  • 存放地址
    • 文件在服务器上的保存地址
    • 要考虑文件的安全性,上传文件应该放在外界无法直接访问的目录下,比如放于WEB-INF目录
    • 要考虑文件覆盖的问题,使用UUID类为上传文件产生一个唯一的文件夹名。文件保存在其中
  • 文件传输
    • 使用FileItem对象的getInputStream方法,获取其中的数据,保存到服务器上
    • 注意:这里使用了缓冲区

处理文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
String uploadFileName = fileItem.getName();//获得文件上传时的文件名(可能包括路径)

//可能存在 文件名不合法 的情况
if(uploadFileName.trim().equals("") || uploadFileName == null){
//trim(),删除字符串的头尾空白符
continue;//不执行下边的语句,跳出for循环,进入下一个for循环(不处理该命名不合法的文件)
}
//获取文件名(不包括路径)
String fileName = uploadFileName.substring(uploadFileName.lastIndexOf("/") + 1);
//lastIndexOf,如果此字符串中没有这样的字符,则返回 -1。说明一整串都是文件名。则全部截取,也是可以截取到文件名
//fileName = (new File(fileItem.getName())).getName();//也可以这样获取文件名(不包括路径)

//获得文件后缀名。jpg、png、gif等
String fileExtName = uploadFileName.substring(uploadFileName.lastIndexOf(".") + 1);
/*
如果文件后缀名fileExtName不是我们所需要的,就直接return,不处理,告诉用户文件类型不对
*/

存放地址

使用UUID(唯一识别的通用码),保证文件夹名唯一。UUID.randomUUID(),随机生成一个唯一识别的通用码

1
2
3
4
5
6
7
8
9
10
11
12
13
String uuidPath = UUID.randomUUID().toString();
//存到哪?uploadPath,上传文件的保存路径
File uploadFile = new File(uploadPath);
if (!uploadFile.exists()){//判断目录是否存在
uploadFile.mkdir();//创建这个目录
}
//文件真实存在的路径 realPath
String realPath = uploadPath + "/" + uuidPath;
//给每个文件创建一个对应的文件夹
File realPathFile = new File(realPath);
if (!realPathFile.exists()){
realPathFile.mkdir();//若该文件夹不存在,则创建文件夹
}

扩展:

1
2
3
4
5
6
网络传输中的东西,都需要序列化(唯一性)。POJO,实体类,如果想要在多个电脑上运行,传输 --> 需要把对象都序列化了,实现接口Serializable

函数式接口:有一个方法的接口
标记接口:没有方法的接口。jvm -->java栈 本地方法栈 native --> c++
Serializable接口,属于标记接口
JNI = Java Native Interface,java本地化接口

文件传输

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//获得文件上传的流
InputStream inputStream = fileItem.getInputStream();
//创建一个文件输出流。realPath = 真实的文件夹。要加上文件名,fileName
FileOutputStream fos = new FileOutputStream(realPath + "/" + fileName);
//创建一个缓冲区
byte[] buffer = new byte[1024*1024];
//判断是否读取完毕
int len = 0;
//如果大于0,说明还存在数据
while ((len = inputStream.read(buffer)) > 0){
fos.write(buffer,0,len);
}
//关闭流
fos.flush();
fos.close();
inputStream.close();

msg = "success!!!";
fileItem.delete();//上传成功,清除临时文件(与方法getDiskFileItemFactory中设置的缓冲区大小、缓存文件夹有关

文件的流操作,可以对比一下文件下载中的代码。(都有使用缓冲区)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//        4. 获取下载文件的输入流
FileInputStream filein = new FileInputStream(realPath);
// 5. 创建缓冲区
int len = 0;
byte[] buffer = new byte[1024];//相当于将文件拆分成一小块一小块的发送
// 6. 获取OutputStream对象
ServletOutputStream out = resp.getOutputStream();
// 7. 将FileOutputStream流写入到buffer缓冲区,使用OutputStream将缓冲区中的数据输出到客户端
while ((len = filein.read(buffer))>0){
out.write(buffer,0,len);//将整个缓冲区内的数据 发送到浏览器
}
// 8.关闭流
filein.close();
out.flush();
out.close();

状态页面

该页面文件放在web应用程序的根目录下,文件名为upload_info.jsp。

1
2
3
4
5
6
7
8
9
10
11
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>上传结果</title>
</head>
<body>

上传结果:${pageContext.request.getParameter("msg")}

</body>
</html>

源码

电脑上使用Java实现文件下载(本地java程序。接收io流):下载文件

通过浏览器下载文件(编写服务器端Servlet程序,发送io流):下载文件

本案例Servlet程序的源码:

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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
package com.qsdbl.jspdemo;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.UUID;

public class FileServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//判断上传的文件是普通表单还是带文件的表单
if (!ServletFileUpload.isMultipartContent(req)){
return;//终止方法运行。说明这是一个普通的表单,直接返回。不处理客户端请求
}//如果通过了这个if,说明我们的表单是带文件的表单

try {
//保存上传文件的目录。
String uploadPath = this.getServletContext().getRealPath("/WEB-INF/upload");
//缓存,临时文件
String tempPath = this.getServletContext().getRealPath("/WEB-INF/temp");


//1、创建DiskFileItemFactory对象,处理文件上传路径或者大小限制的
DiskFileItemFactory factory = getDiskFileItemFactory(tempPath);
//2、获取ServletFileUpload
ServletFileUpload upload = getServletFileUpload(factory);
//3、处理上传的文件
String msg = uploadParseRequest(upload,req,uploadPath);


//重定向
resp.sendRedirect(req.getContextPath()+"/upload_info.jsp?msg="+msg);
}catch (Exception e){
e.printStackTrace();
}
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
private DiskFileItemFactory getDiskFileItemFactory(String tempPath){
//1、创建DiskFileItemFactory对象,处理文件上传路径或者大小限制
DiskFileItemFactory factory = new DiskFileItemFactory();

//扩展:
//通过这个工厂设置一个缓冲区,当上传的文件大于这个缓冲区的时候,将它放到 临时文件 中(减少内存占用)
factory.setSizeThreshold(1024*1024);//缓存区大小为1M(单位为字节)(文件大于该大小将会生成一个缓存文件放到缓存目录下,传输完毕后记得要使用delete删掉)
File tempFile = new File(tempPath);
if (!tempFile.exists()){//判断目录是否存在
tempFile.mkdir();//创建这个目录
}
factory.setRepository(tempFile);//临时文件的保存目录,需要一个File对象
return factory;
}
private ServletFileUpload getServletFileUpload(DiskFileItemFactory factory){
//2、获取ServletFileUpload
ServletFileUpload upload = new ServletFileUpload(factory);

//扩展:
//监听文件上传进度
upload.setProgressListener(new ProgressListener() {
@Override
//pBytesRead,已读取到的文件大小
//pContentLent,文件大小
public void update(long pBytesRead, long pContentLent, int i) {
// System.out.println("总大小:"+pContentLent+" 已经上传:"+pBytesRead);
System.out.println("上传进度:"+String.format("%.0f",(pBytesRead/1.0/pContentLent)*100)+"%");
}
});
//处理乱码问题
upload.setHeaderEncoding("utf-8");
//设置单个文件的最大值(大于下边设置的值,会抛出异常)
// upload.setFileSizeMax(1024*1024*10);//10M
//设置总共能够上传文件的大小
// upload.setSizeMax(1024*1024*10);//10M

return upload;
}
private String uploadParseRequest(ServletFileUpload upload,HttpServletRequest req,String uploadPath){
String msg = "fail!!!";//若上传成功,则会替换掉该提示消息
//io操作
try {
//3、处理上传的文件
//把前端请求解析封装成一个FileItem对象,需要从ServletFileUpload对象中获取
List<FileItem> fileItems = upload.parseRequest(req);//将表单中每一个HTML标签提交的数据封装成一个FileItem对象
for (FileItem fileItem : fileItems) {
//判断上传的文件(表单中的子项)是普通的表单项还是带文件的表单项
if (fileItem.isFormField()){//该表单项是普通类型
//getFieldName指的是前端表单 控件的name。getString,获取控件中输入的值
String name = fileItem.getFieldName();
String value = fileItem.getString("utf-8");//指定编码类型为utf-8,避免中文乱码
System.out.println(name+":"+value);
}else {//该表单项是file类型


//- - - - - - - - 处理文件 - - - - - - - -
// fileItem.getFieldName(),控件的name。fileItem.getName(),file控件选择的文件的 文件名
String uploadFileName = fileItem.getName();//获得文件上传时的文件名(可能包括路径)
System.out.println("上传的文件:"+uploadFileName);

//可能存在文件名不合法的情况
if(uploadFileName.trim().equals("") || uploadFileName == null){
//trim(),删除字符串的头尾空白符
continue;//不执行下边的语句,跳出for循环,进入下一个for循环(不处理该命名不合法的文件)
}
//获取文件名(不包括路径)
String fileName = uploadFileName.substring(uploadFileName.lastIndexOf("/") + 1);
//lastIndexOf,如果此字符串中没有这样的字符,则返回 -1。说明一整串都是文件名。则全部截取,也是可以截取到文件名
//fileName = (new File(fileItem.getName())).getName();//也可以这样获取文件名(不包括路径)

//获得文件后缀名。jpg、png、gif等
String fileExtName = uploadFileName.substring(uploadFileName.lastIndexOf(".") + 1);
/*
如果文件后缀名fileExtName不是我们所需要的,就直接return,不处理,告诉用户文件类型不对
*/

//可以使用UUID(唯一识别的通用码),保证文件名唯一。UUID。randomUUID(),随机生成一个唯一识别的通用码
//网络传输中的东西,都需要序列化(唯一性)。
//POJO,实体类,如果想要在多个电脑上运行,传输 --> 需要把对象都序列化了,实现接口Serializable
//扩展:
//函数式接口:有一个方法的接口
//标记接口:没有方法的接口。jvm -->java栈 本地方法栈 native --> c++
//Serializable接口,属于标记接口
//JNI = Java Native Interface,java本地化接口

String uuidPath = UUID.randomUUID().toString();


//- - - - - - - - 存放地址 - - - - - - - -
//存到哪?uploadPath,上传文件的保存路径
File uploadFile = new File(uploadPath);
if (!uploadFile.exists()){//判断目录是否存在
uploadFile.mkdir();//创建这个目录
}
//文件真实存在的路径 realPath
String realPath = uploadPath + "/" + uuidPath;
//给每个文件创建一个对应的文件夹
File realPathFile = new File(realPath);
if (!realPathFile.exists()){
realPathFile.mkdir();//若该文件夹不存在,则创建文件夹
}


//- - - - - - - - 文件传输 - - - - - - - -
//获得文件上传的流
InputStream inputStream = fileItem.getInputStream();
//创建一个文件输出流。realPath = 真实的文件夹。要加上文件名,fileName
FileOutputStream fos = new FileOutputStream(realPath + "/" + fileName);
//创建一个缓冲区
byte[] buffer = new byte[1024*1024];
//判断是否读取完毕
int len = 0;
//如果大于0,说明还存在数据
while ((len = inputStream.read(buffer)) > 0){
fos.write(buffer,0,len);
}
//关闭流
fos.flush();
fos.close();
inputStream.close();

msg = "success!!!";
fileItem.delete();//上传成功,清除临时文件(与方法getDiskFileItemFactory中设置的缓冲区大小、缓存文件夹有关
System.out.println("上传文件,保存路径:"+realPath+"/"+fileName);//测试用
}
}
}catch (Exception e){
e.printStackTrace();
return msg+" The file is too large.";//与方法getServletFileUpload中设置的限制文件大小有关
}
return msg;
}
}
若图片不能正常显示,请在浏览器中打开

欢迎关注我的其它发布渠道