JavaWeb:文件上传(详)
文件上传
一、注意事项
- 为保证服务器安全,上传文件应当保存在外界无法直接访问的路径(如 WEB-INF 目录下)
- 为防止文件覆盖,要为上传的文件生成一个唯一的文件名(如-时间戳,-uuid,-md5,-位运算算法)
- 要限制上传文件的大小的最大值。
- 可以限制上传文件的类型,在获取上传文件名时,判断后缀名是否合法。
二、文件上传
1、组件
浏览器处理上传文件,是将文件以流的形式提交到服务器端。
commons-fileupload
:Apache 的文件上传组件,取代原生的文件上传流。commons-io
:commons-fileupload 组件依赖于该组件。
2、前端表单
form 表单
- 提交方式:
method=“post”
(post传送的数据量大,可视为不受限制) - 编码类型:
enctype="multipart/form-data"
(表单包含文件上传控件时必须使用)- enctype 属性
application/x-www=form-urlencoded
:默认方式,只处理表单域中的 value 属性值,将表单域中的值处理成 URL 编码方式;multipart/form-data
:以二进制流的方式处理表单数据,除了表单域中的 value 属性值,还会处理表单域的文件内容,将其封装到请求参数中,不会对字符编码;text/plain
:将空格转换为+
号,其它字符不做编码处理,适用于通过表单发送邮件。
- enctype 属性
input 输入框
-
类型:
type=“file”
-
属性:要求具有 name 属性
3、实用类介绍
注:
- 一个表单项(即一个字段)会被解析为一个 FileItem 对象;
- 也就是说,一个 FileItem 对象封装了一个表单项。
DiskFileItemFactory 类
- 设置临时文件夹;
- 设置文件缓存区大小。
// 1、设置临时文件夹
void setRepository(File repository)
// *2、设置文件缓存区大小
void setSizeThreshold(int sizeThreshold)
ServletFileUpload 类
- 判断表单是否包含文件上传控件:即是否设置了
enctype="multipart/form-data"
; - 设置 FileItemFactory 属性;
- 解析前端请求:将表单项解析成 FileItem 对象;
- *监听文件上传进度、处理乱码问题、设置文件上传最大值
// 1、静态方法:判断表单是否包含文件上传控件,负责处理文件数据
static boolean isMultipartContent(HttpServletRequest request)
// 2、父类方法:设置FileItemFactory属性,也可通过构造方法设置
void setFileItemFactory(FileItemFactory factory)
// 3、解析前端请求,将每个表单项解析并封装成FileItem对象,以List列表的形式返回。
List<FileItem> parseRequest(HttpServletRequest request)
// *4、父类方法:监听文件上传进度
void setProgressListener(ProgressListener pListener)
// *5、父类方法:处理乱码问题
void setHeaderEncoding(String encoding)
// *6、父类方法:设置单个文件的最大值
void setFileSizeMax(long fileSizeMax)
// *7、父类方法:设置总共能够上传的文件大小
void setSizeMax(long sizeMax)
DiskFileItem:FileItem 接口实现类
- 判断表单项是否上传文件,即类型是否为
type=“file”
; - 获取字段名;
- 获取数据流内容;
- 获取上传文件名;
- 获取上传文件输入流;
- 清空 FileItem 对象。
// 1、判断表单项是否为上传文件:普通文本返回true,否则返回false
boolean isFormField();
// 2、获取字段名:表单项name属性的值
String getFieldName();
// 3、FileItem对象中保存的数据流内容:即表单项为普通文本的输入值
String getString();
// 4、获取表单项为文件上传的文件名
String getName();
// 5、获取上传文件的输入流
InputStream getInputStream();
// 6、清空FileItem类对象中存放的主体内容,如果主体内容被保存在临时文件中,delete方法将删除该临时文件
void delete();
4、实现步骤
带 * 号的是用于完善业务的辅助功能,不是必要的。
- 判断表单:是否包含文件上传控件。
- 创建上传文件和临时文件的保存路径。
- 创建 DiskFileItemFactory 对象。
- 设置临时文件夹
- *设置缓冲区大小
- 创建 ServletFileUpload 对象
- 设置 FileItemFactory
- *监听文件上传进度、处理乱码问题、设置单个文件和总共上传文件的最大值
- 解析请求并处理文件传输
- 解析前端请求,将每个表单项解析并封装成 FileItem 对象
- 判断表单项是否为上传文件
- 处理普通文本:
- 获取字段名
- 获取数据流内容
- 处理文件:
- 获取上传文件名
- 生成随机 UUID 作为文件存储路径,并为其生成文件夹
- 通过 IO 流传输文件
三、代码实现
1、导入Maven依赖
(普通项目则导入jar包)
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
2、前端页面
fileUpload.jsp
<form action="${pageContext.request.contextPath}/upload.do" method="post" enctype="multipart/form-data">
<p>上传用户:<input type="text" name="username"></p>
<p><input type="file" name="file"></p>
<p>
<input type="submit">|<input type="reset">
</p>
</form>
success.jsp
<h1>${msg}</h1>
3、Servlet
public class FileServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 判断表单是否包含文件上传控件
if (!ServletFileUpload.isMultipartContent(req)) { // 不包含文件上传控件,即普通表单
return;
}
// 创建上传文件的保存路径:外界无法直接访问
String uploadPath = this.getServletContext().getRealPath("/WEB-INF/upload");
File uploadFile = new File(uploadPath);
makeDirIfNotExist(uploadFile);
// 创建临时文件的保存路径
String tmpPath = this.getServletContext().getRealPath("/WEB-INF/tmp");
File tmpFile = new File(tmpPath);
makeDirIfNotExist(tmpFile);
// 1、创建DiskFileItemFactory对象
DiskFileItemFactory factory = getDiskFileItemFactory(tmpFile);
// 2、创建ServletFileUpload对象
ServletFileUpload servletFileUpload = getServletFileUpload(factory);
// 3、解析请求并处理文件传输
String msg = "";
try {
msg = uploadParseRequest(req, servletFileUpload, uploadPath);
} catch (FileUploadException e) {
e.printStackTrace();
}
req.setAttribute("msg", msg);
req.getRequestDispatcher("/success.jsp").forward(req, resp);
}
private String uploadParseRequest(HttpServletRequest httpServletRequest, ServletFileUpload servletFileUpload, String uploadPath)
throws FileUploadException, IOException {
String msg = "";
// 解析前端请求,将每个表单项解析并封装成FileItem对象
List<FileItem> fileItems = servletFileUpload.parseRequest(httpServletRequest);
for (FileItem fileItem : fileItems) {
// 判断表单项是否为上传文件
if (fileItem.isFormField()) { // 普通文本
String filedName = fileItem.getFieldName(); // 获取字段名:表单项name属性的值
String value = fileItem.getString("UTF-8"); // FileItem对象中保存的数据流内容:即表单项为普通文本的输入值
System.out.println(filedName + ":" + value);
} else { // 上传文件
// ------------------------1、处理文件------------------------
String uploadFileName = fileItem.getName();// 上传文件名
System.out.println("上传的文件名:" + uploadFileName);
if (uploadFileName == null || uploadFileName.trim().equals("")) { // 文件名为空值或空
continue; // 进入下一轮循环,判断下一个FileItem对象
}
String fileExtName = uploadFileName.substring(uploadFileName.lastIndexOf(".") + 1); // 文件后缀名
System.out.println("文件信息【文件名:" + uploadFileName + ",文件类型:" + fileExtName + "】");
// 使用UUID保证文件名唯一
String uuidPath = UUID.randomUUID().toString();
// ------------------------2、处理路径------------------------
// 存储路径:uploadPath
String realPath = uploadPath + '/' + uuidPath; // 真实存在的路径
// 给每个文件创建一个文件夹
File realPathFile = new File(realPath);
makeDirIfNotExist(realPathFile);
// ------------------------3、文件传输------------------------
// 获得输入流
InputStream is = fileItem.getInputStream();
// 获得输出流
FileOutputStream fos = new FileOutputStream(realPathFile + "/" + uploadFileName);
// 创建缓冲区
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
msg = "文件上传成功!";
fileItem.delete(); // 上传成功,清除临时文件
fos.close();
is.close();
}
}
return msg;
}
private ServletFileUpload getServletFileUpload(DiskFileItemFactory factory) {
// ServletFileUpload servletFileUpload = new ServletFileUpload(factory); // 构造方法设置FileItemFactory
ServletFileUpload servletFileUpload = new ServletFileUpload();
servletFileUpload.setFileItemFactory(factory); // 设置FileItemFactory
// ------------------------辅助功能------------------------
// 监听文件上传进度
servletFileUpload.setProgressListener(new ProgressListener() {
/**
*
* @param pBytesRead 已读取的文件大小
* @param pContentLength 文件大小
* @param pItems
*/
@Override
public void update(long pBytesRead, long pContentLength, int pItems) {
String percentage = (int) (((double) pBytesRead / pContentLength) * 100) + "%";
System.out.println("总大小:" + pContentLength + ",已上传:" + pBytesRead + "\t" + percentage);
}
});
// 处理乱码问题
servletFileUpload.setHeaderEncoding("UTF-8");
// 设置单个上传文件的最大值
servletFileUpload.setFileSizeMax(1024 * 1024 * 10); // 10M
// 设置总共上传文件的最大值
servletFileUpload.setSizeMax(1024 * 1024 * 10); // 10M
return servletFileUpload;
}
/**
* 获得磁盘文件项目工程,设置缓冲文件夹及缓冲区大小
*
* @param tmpFile 缓冲文件夹
* @return
*/
private DiskFileItemFactory getDiskFileItemFactory(File tmpFile) {
// return new DiskFileItemFactory(1024 * 1024, tmpFile);
DiskFileItemFactory factory = new DiskFileItemFactory();
// ------------------------辅助功能------------------------
factory.setSizeThreshold(1024 * 1024); // 1M(缓冲区大小):上传文件大于缓冲区大小时,fileupload组件将使用临时文件缓存上传文件
factory.setRepository(tmpFile); // 临时文件夹
return factory;
}
/**
* 如果文件目录不存在,为其创建目录
*
* @param file
*/
private void makeDirIfNotExist(File file) {
if (!file.exists()) {
file.mkdir();
}
}
}
4、注册Servlet
<servlet>
<servlet-name>FileServlet</servlet-name>
<servlet-class>indi.jaywee.file.FileServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FileServlet</servlet-name>
<url-pattern>/upload.do</url-pattern>
</servlet-mapping>
5、运行结果
上传的文件会保存在 Target 目录下对应项目的保存路径下。
非临时文件:
- 保存在 upload 文件夹下。
- 文件格式和文件名与上传文件完全相同。
- 每个文件存储在一个子文件夹中,文件夹名是随机生成的 UUID。
临时文件:
- 保存在 upload 文件夹下,同时在tmp文件夹下生成一个临时文件。
- upload 文件夹下的文件:文件格式和文件名与上传文件完全相同。
- tmp 文件夹下的文件:tmp 格式,文件名是随机生成的 UUID,在 Servlet 运行到 fileItem.delete() 方法时被清除。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)