核心概念
-
前端 (jQuery):
(图片来源网络,侵删)- HTML: 创建一个
<input type="file">元素让用户选择文件。 - JavaScript/jQuery: 监听表单提交或按钮点击事件,阻止默认的表单提交行为,然后使用
FormData对象来收集文件数据。 - AJAX: 通过
$.ajax将FormData对象异步发送到后端服务器。
- HTML: 创建一个
-
后端 (Java):
- Servlet: 接收前端发送的 HTTP 请求,文件数据会以
multipart/form-data的格式封装在请求体中。 - Apache Commons FileUpload: 这是一个非常流行且强大的库,专门用于解析
multipart/form-data请求,从中提取出文件和其他表单字段。 - 处理: 将接收到的文件保存服务器的指定目录。
- 响应: 返回一个 JSON 或其他格式的响应,告诉前端上传成功或失败。
- Servlet: 接收前端发送的 HTTP 请求,文件数据会以
第一步:项目环境准备
我们使用一个简单的 Maven Web 项目。
后端依赖 (pom.xml)
你需要添加 Servlet API 和 Apache Commons FileUpload 的依赖。

<dependencies>
<!-- Servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!-- Apache Commons FileUpload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<!-- 为了方便返回JSON,可以使用Gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
</dependencies>
前端库
在你的 HTML 页面中引入 jQuery。
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
第二步:前端实现 (HTML + jQuery)
创建一个 index.html 文件。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">jQuery 文件上传示例</title>
<style>
body { font-family: sans-serif; }
.container { margin: 20px; }
#progressBar { width: 300px; height: 20px; border: 1px solid #ccc; }
#progressBar div { width: 0%; height: 100%; background-color: green; }
#message { margin-top: 10px; }
</style>
</head>
<body>
<div class="container">
<h2>文件上传</h2>
<form id="uploadForm" enctype="multipart/form-data">
<input type="file" id="fileInput" name="file" required>
<button type="submit">上传</button>
</form>
<div>
<h4>上传进度:</h4>
<div id="progressBar">
<div id="progress"></div>
</div>
<p id="message"></p>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
$(document).ready(function() {
$('#uploadForm').on('submit', function(e) {
// 1. 阻止表单默认提交行为
e.preventDefault();
// 2. 获取文件输入框中的文件
var fileInput = $('#fileInput')[0];
var file = fileInput.files[0];
if (!file) {
$('#message').text('请选择一个文件!');
return;
}
// 3. 创建 FormData 对象
var formData = new FormData();
formData.append('file', file);
// 4. 显示上传进度条
$('#message').text('正在上传...');
$('#progress').css('width', '0%');
// 5. 使用 AJAX 发送文件
$.ajax({
url: '/FileUploadDemo/upload', // 对应后端 Servlet 的 URL
type: 'POST',
data: formData,
// 关键:必须设置为 false,因为 jQuery 默认会处理数据并设置 Content-Type,
// 但文件上传需要浏览器自己设置 'multipart/form-data' 和 boundary。
processData: false,
// 关键:必须设置为 false,因为 FormData 对象不需要 jQuery 来设置 Content-Type。
contentType: false,
// 上传进度回调
xhr: function() {
var xhr = new window.XMLHttpRequest();
xhr.upload.addEventListener('progress', function(e) {
if (e.lengthComputable) {
var percent = Math.round((e.loaded / e.total) * 100);
$('#progress').css('width', percent + '%');
$('#message').text('上传进度: ' + percent + '%');
}
}, false);
return xhr;
},
// 成功回调
success: function(response) {
$('#message').text('上传成功: ' + response.message);
$('#progress').css('width', '100%');
// 清空文件输入框,允许再次上传同一文件
$('#fileInput').val('');
},
// 错误回调
error: function(jqXHR, textStatus, errorThrown) {
$('#message').text('上传失败: ' + textStatus + ' - ' + errorThrown);
}
});
});
});
</script>
</body>
</html>
前端代码关键点解释:

enctype="multipart/form-data": 必须 在<form>标签中设置,否则文件无法正确上传。e.preventDefault(): 阻止表单用传统方式刷新页面,实现 AJAX 无刷新上传。new FormData(): 创建一个表单数据对象,用于封装文件。formData.append('file', file): 将文件添加到FormData中,'file'这个名字需要和后端解析时使用的名字一致。processData: false和contentType: false: 这两个是 AJAX 上传文件的核心配置。 告诉 jQuery 不要对data进行任何处理,也不要设置Content-Type头,让浏览器使用正确的multipart/form-data格式。xhr.upload.addEventListener('progress', ...): 这是实现上传进度条的关键,通过监听XMLHttpRequest对象的upload事件,我们可以实时获取上传的字节数和总字节数。
第三步:后端实现 (Java Servlet)
创建一个 FileUploadServlet.java。
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import java.util.UUID;
import com.google.gson.Gson;
@WebServlet("/upload")
public class FileUploadServlet extends HttpServlet {
// 上传文件存储目录
private static final String UPLOAD_DIRECTORY = "uploads";
// 上传配置
private static final int MEMORY_THRESHOLD = 1024 * 1024 * 3; // 3MB
private static final int MAX_FILE_SIZE = 1024 * 1024 * 40; // 40MB
private static final int MAX_REQUEST_SIZE = 1024 * 1024 * 50; // 50MB
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 检查是否为多媒体上传
if (!ServletFileUpload.isMultipartContent(request)) {
// 如果不是则停止
PrintWriter writer = response.getWriter();
writer.println("Error: 表单必须包含 enctype=multipart/form-data");
writer.flush();
return;
}
// 配置上传参数
DiskFileItemFactory factory = new DiskFileItemFactory();
// 设置内存临界值 - 超过后将产生临时文件存储于临时目录
factory.setSizeThreshold(MEMORY_THRESHOLD);
// 设置临时存储目录
factory.setRepository(new File(System.getProperty("java.io.tmpdir")));
ServletFileUpload upload = new ServletFileUpload(factory);
// 设置最大文件上传值
upload.setFileSizeMax(MAX_FILE_SIZE);
// 设置最大请求值 (包含文件和表单数据)
upload.setSizeMax(MAX_REQUEST_SIZE);
// 构建临时上传目录
String uploadPath = getServletContext().getRealPath("") + File.separator + UPLOAD_DIRECTORY;
File uploadDir = new File(uploadPath);
if (!uploadDir.exists()) {
uploadDir.mkdir();
}
try {
// 解析请求的内容提取文件数据
@SuppressWarnings("unchecked")
List<FileItem> formItems = upload.parseRequest(request);
if (formItems != null && formItems.size() > 0) {
for (FileItem item : formItems) {
// 处理不在表单中的字段
if (!item.isFormField()) {
String fileName = new File(item.getName()).getName();
String filePath = uploadPath + File.separator + fileName;
// 为了防止文件名覆盖,使用UUID重命名
String fileExtension = fileName.substring(fileName.lastIndexOf("."));
String uniqueFileName = UUID.randomUUID().toString() + fileExtension;
String finalFilePath = uploadPath + File.separator + uniqueFileName;
// 保存文件到服务器
item.write(new File(finalFilePath));
// 返回成功响应
String jsonResponse = new Gson().toJson(
new UploadResponse("文件 " + uniqueFileName + " 上传成功!", finalFilePath)
);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(jsonResponse);
}
}
}
} catch (Exception ex) {
// 打印错误信息
ex.printStackTrace();
// 返回错误响应
String errorJsonResponse = new Gson().toJson(
new UploadResponse("文件上传失败: " + ex.getMessage(), null)
);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.getWriter().write(errorJsonResponse);
}
}
// 用于封装响应信息的内部类
private static class UploadResponse {
private String message;
private String filePath;
public UploadResponse(String message, String filePath) {
this.message = message;
this.filePath = filePath;
}
public String getMessage() { return message; }
public String getFilePath() { return filePath; }
}
}
后端代码关键点解释:
@WebServlet("/upload"): 将这个 Servlet 映射到/upload路径,与前端 AJAX 的url对应。isMultipartContent(request): 检查请求是否为multipart/form-data类型。DiskFileItemFactory: 创建一个文件项工厂,用于配置上传过程中的内存和临时文件处理。MEMORY_THRESHOLD: 当上传的文件超过这个大小时,文件会被写入到临时目录,而不是全部保存在内存中,防止内存溢出。
ServletFileUpload: 这是核心类,用于解析multipart请求。setFileSizeMax: 限制单个文件的大小。setSizeMax: 限制整个请求的大小(包括所有文件和表单字段)。
uploadPath: 定义文件在服务器上的存储路径。getServletContext().getRealPath("")可以获取 Web 应用的根目录路径。item.isFormField(): 判断当前项是普通表单字段还是文件。item.getName(): 获取上传的文件名。UUID.randomUUID().toString(): 生成一个唯一的文件名,防止因文件名相同而导致的文件覆盖问题。item.write(new File(finalFilePath)): 将文件内容写入到服务器的指定位置。- JSON 响应: 使用
Gson库将一个简单的 Java 对象转换成 JSON 字符串返回给前端,方便前端处理。
第四步:部署和运行
- 将项目打包成 WAR 文件。
- 部署到 Tomcat 或其他支持 Servlet 的 Web 服务器中。
- 启动服务器。
- 在浏览器中访问
http://localhost:8080/你的项目名/index.html。 - 选择一个文件,点击上传,你将看到进度条变化,并最终收到成功或失败的提示。
安全性与最佳实践
-
文件类型验证:
- 不要仅仅依赖文件扩展名,因为它是可以伪造的。
- 读取文件的“魔数”(Magic Number),即文件头部的几个字节,来判断真实的文件类型。
- 在后端,可以维护一个允许上传的白名单(如
jpg,png,pdf,docx),如果文件类型不在白名单中,则直接拒绝。
-
文件大小限制:
- 我们已经在后端设置了
MAX_FILE_SIZE,这是非常重要的防御措施,可以防止用户上传超大文件耗尽服务器磁盘空间或导致 DOS 攻击。
- 我们已经在后端设置了
-
文件名处理:
- 我们已经使用了
UUID来重命名文件,这是一个很好的实践,可以防止恶意文件名(如../../../etc/passwd)导致的路径遍历攻击。
- 我们已经使用了
-
病毒扫描:
对于生产环境,上传的文件应该经过杀毒软件的扫描,确保文件不包含恶意代码。
-
存储位置:
- 不要将上传的文件直接放在 Web 服务器的根目录下(如
webapps),这样用户可能通过 URL 直接访问到这些文件,除非你明确希望如此,最好是放在一个非 Web 可达的目录,然后通过一个专门的 Servlet 来提供文件下载服务,并在此进行权限控制。
- 不要将上传的文件直接放在 Web 服务器的根目录下(如
通过以上步骤,你就实现了一个功能完整、带有进度条、并且具备基本安全性的文件上传功能。
