核心概念
- 前端: 使用 HTML 创建表单,使用 jQuery 来捕获用户操作,并通过 AJAX 将文件数据发送到服务器。
- 后端: 使用 Java 技术栈(如 Servlet 或 Spring Boot)来接收前端传来的文件数据,并将其保存到服务器上。
传统表单提交 (Form Submission)
这是最简单、最兼容的方式,但缺点是页面会刷新或跳转。
前端代码 (HTML & jQuery)
这里我们使用 jQuery 来提交表单,可以做一些简单的验证。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">传统文件上传</title>
<!-- 引入 jQuery -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<h2>传统方式上传文件</h2>
<form id="uploadForm" action="upload" method="post" enctype="multipart/form-data">
<!--
注意:form 的 enctype 必须是 "multipart/form-data"
才能正确上传文件
-->
<input type="file" name="file" id="fileInput">
<button type="submit">上传</button>
</form>
<div id="message" style="margin-top: 10px; color: green;"></div>
<script>
$(document).ready(function() {
$('#uploadForm').on('submit', function(e) {
// 可以在这里添加一些简单的验证
if ($('#fileInput').val() == '') {
$('#message').text('请先选择一个文件!');
e.preventDefault(); // 阻止表单提交
return;
}
// 使用 jQuery 的 serialize() 方法来序列化表单数据
// 但注意:serialize() 对于文件上传可能不是最佳选择,
// 因为它不能直接处理文件对象,这里我们让浏览器自己处理。
// 我们只是阻止默认行为,然后手动触发提交。
// 如果需要显示进度,这里会更复杂,通常用 AJAX。
// 这里我们直接让表单按默认方式提交。
// 服务器响应后,页面会刷新或跳转。
});
});
</script>
</body>
</html>
后端代码 (Java - Servlet)
使用传统的 Java Servlet 来接收文件。
Maven 依赖 (pom.xml)
<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>
<!-- Commons IO, 用于辅助操作 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
</dependencies>
Servlet 代码 (UploadServlet.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.util.List;
@WebServlet("/upload")
public class UploadServlet 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 {
// 检查是否为 multipart/form-data 请求
if (!ServletFileUpload.isMultipartContent(request)) {
// 如果不是,则停止处理
response.getWriter().println("Error: 表单必须是 multipart/form-data 类型");
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;
File storeFile = new File(filePath);
// 在控制台输出上传文件的信息
System.out.println("文件上传到: " + filePath);
// 保存文件到硬盘
item.write(storeFile);
}
}
}
// 上传成功,重定向到成功页面或返回成功信息
request.setAttribute("message", "文件上传成功!");
getServletContext().getRequestDispatcher("/message.jsp").forward(request, response);
} catch (Exception ex) {
request.setAttribute("message", "错误信息: " + ex.getMessage());
getServletContext().getRequestDispatcher("/message.jsp").forward(request, response);
}
}
}
AJAX 异步上传 (推荐)
这种方式用户体验更好,页面不会刷新,可以显示上传进度,并在上传完成后动态更新页面内容。
前端代码 (HTML & jQuery)
关键点:
- 使用
FormData对象来构建要发送的数据,它可以正确地包含文件。 - 设置 AJAX 请求的
processData和contentType为false。 - 使用
xhr对象来监听上传进度。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">AJAX 文件上传</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<style>
#progressBar { width: 300px; height: 20px; background-color: #f3f3f3; border-radius: 5px; }
#progressBar div { width: 0%; height: 100%; background-color: #4CAF50; border-radius: 5px; text-align: center; line-height: 20px; color: white; }
</style>
</head>
<body>
<h2>AJAX 方式上传文件</h2>
<input type="file" id="fileInput">
<button id="uploadButton">上传</button>
<div id="progressBar">
<div id="progress"></div>
</div>
<div id="message" style="margin-top: 10px;"></div>
<script>
$(document).ready(function() {
$('#uploadButton').on('click', function() {
var fileInput = $('#fileInput')[0];
var file = fileInput.files[0];
if (!file) {
$('#message').text('请先选择一个文件!').css('color', 'red');
return;
}
// 创建 FormData 对象
var formData = new FormData();
formData.append('file', file); // 'file' 必须与后端接收的参数名一致
// 禁用上传按钮,防止重复提交
$(this).prop('disabled', true);
$.ajax({
url: 'ajaxUpload', // Servlet 的映射路径
type: 'POST',
data: formData,
// 必须设置为 false,因为 jQuery 默认会将数据转换为 query string
processData: false,
// 必须设置为 false,因为 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 + '%').text(percent + '%');
}
}, false);
return xhr;
},
success: function(response) {
$('#message').text(response.message).css('color', 'green');
// 清空文件输入
$('#fileInput').val('');
},
error: function(jqXHR, textStatus, errorThrown) {
$('#message').text('上传失败: ' + textStatus).css('color', 'red');
},
complete: function() {
// 重新启用上传按钮
$('#uploadButton').prop('disabled', false);
}
});
});
});
</script>
</body>
</html>
后端代码 (Java - Servlet)
后端代码与方案一基本相同,只需要修改一下 Servlet 的映射路径 (@WebServlet) 和返回给前端的数据格式(通常是 JSON)。
Servlet 代码 (AjaxUploadServlet.java)
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import com.google.gson.Gson; // 引入 Gson 库来转换 JSON
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.util.HashMap;
import java.util.List;
import java.util.Map;
@WebServlet("/ajaxUpload")
public class AjaxUploadServlet extends HttpServlet {
private static final String UPLOAD_DIRECTORY = "uploads";
private static final int MEMORY_THRESHOLD = 1024 * 1024 * 3;
private static final int MAX_FILE_SIZE = 1024 * 1024 * 40;
private static final int MAX_REQUEST_SIZE = 1024 * 1024 * 50;
// 使用 Gson 来处理 JSON
private Gson gson = new Gson();
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 设置响应内容类型为 JSON
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
Map<String, String> resultMap = new HashMap<>();
if (!ServletFileUpload.isMultipartContent(request)) {
resultMap.put("success", "false");
resultMap.put("message", "表单必须是 multipart/form-data 类型");
response.getWriter().write(gson.toJson(resultMap));
return;
}
// ... (中间的文件处理逻辑与方案一的 UploadServlet 完全相同) ...
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;
File storeFile = new File(filePath);
item.write(storeFile);
}
}
}
resultMap.put("success", "true");
resultMap.put("message", "文件上传成功!");
} catch (Exception ex) {
resultMap.put("success", "false");
resultMap.put("message", "错误信息: " + ex.getMessage());
}
// 将 Map 转换为 JSON 字符串并返回
response.getWriter().write(gson.toJson(resultMap));
}
}
Maven 依赖 (需要添加 Gson)
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.9</version>
</dependency>
Spring Boot 实现文件上传 (更现代的方式)
如果你使用的是 Spring Boot,处理文件上传会变得非常简单。
前端代码
前端代码与方案二的 AJAX 完全相同,无需改动。
后端代码 (Spring Boot)
Maven 依赖 (pom.xml)
Spring Boot Web starter 已经包含了处理文件上传所需的核心依赖。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
Controller 代码 (FileUploadController.java)
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
@RestController
public class FileUploadController {
// 定义一个上传目录,在实际项目中最好配置在 application.properties 中
private static final String UPLOADED_FOLDER = "uploads/";
@PostMapping("/springUpload")
public ResponseEntity<?> handleFileUpload(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return new ResponseEntity<>("请选择一个文件", HttpStatus.BAD_REQUEST);
}
try {
// 创建上传目录
Path uploadPath = Paths.get(UPLOADED_FOLDER);
if (!Files.exists(uploadPath)) {
Files.createDirectories(uploadPath);
}
// 获取文件名并构建目标路径
String fileName = file.getOriginalFilename();
Path filePath = uploadPath.resolve(fileName);
// 保存文件
Files.copy(file.getInputStream(), filePath);
Map<String, String> response = new HashMap<>();
response.put("success", "true");
response.put("message", "文件上传成功: " + fileName);
response.put("path", filePath.toString());
return ResponseEntity.ok(response);
} catch (IOException e) {
e.printStackTrace();
return new ResponseEntity<>("上传失败: " + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
总结与对比
| 特性 | 传统表单 | AJAX + Servlet | Spring Boot |
|---|---|---|---|
| 用户体验 | 差,页面会刷新或跳转 | 好,无刷新,可显示进度 | 好,无刷新,可显示进度 |
| 实现复杂度 | 简单 | 中等,需要处理 FormData 和 xhr |
非常简单,Spring Boot 封装得很好 |
| 后端技术 | 原生 Servlet | 原生 Servlet | Spring Boot / Spring MVC |
| 数据返回 | HTML 页面重定向 | JSON | JSON |
| 适用场景 | 简单、旧项目,对用户体验要求不高 | 大多数 Web 应用,推荐使用 | 基于 Spring 生态的现代项目 |
推荐选择:
- 对于新项目,强烈推荐 方案三 (Spring Boot),因为它最简洁、功能最强大。
- 如果你不使用 Spring,或者需要在一个旧的非 Spring 项目中添加文件上传功能,方案二 (AJAX + Servlet) 是最佳选择。
