杰瑞科技汇

jQuery文件上传如何与Java后端交互?

这个教程将覆盖从最基础的表单上传到更现代的 AJAX 无刷新上传,并包含进度条、后端验证、文件大小限制等常见场景。

jQuery文件上传如何与Java后端交互?-图1
(图片来源网络,侵删)

整体思路

  1. 前端 (HTML + jQuery + AJAX):

    • 创建一个包含文件输入框的 HTML 表单。
    • 使用 jQuery 监听表单的提交事件(或按钮的点击事件)。
    • 阻止表单的默认提交行为(避免页面刷新)。
    • 使用 FormData 对象来封装文件数据和其他表单字段。
    • 通过 $.ajax()FormData 发送到 Java 后端。
    • (可选)监听上传进度事件,显示进度条。
    • 接收并处理后端返回的 JSON 响应,提示用户上传成功或失败。
  2. 后端 (Java + Servlet):

    • 创建一个 Servlet 来处理 /upload 这样的 URL 请求。
    • 使用 Part 对象(Servlet 3.0+ API)来接收前端传来的文件。
    • 获取文件名、文件大小、输入流等信息。
    • 进行后端校验(如文件类型、文件大小)。
    • 将文件保存到服务器的指定目录(如 uploads 文件夹)。
    • 返回一个 JSON 响应给前端,告知上传结果(成功/失败、文件信息等)。

准备工作

  1. 项目环境:
    • 一个标准的 Java Web 项目(使用 Maven 或 Gradle)。
    • 一个 Servlet 容器(如 Tomcat, Jetty)。
  2. 依赖库:
    • jQuery: 在你的 HTML 页面中引入 jQuery 库。
    • Servlet API: 如果你使用 Maven,它会自动管理,如果你是手动搭建,确保 servlet-api.jar 在你的 WEB-INF/lib 目录下。
    • JSON 库: 为了方便返回 JSON 格式的数据,推荐使用 JacksonGson,这里我们以 Jackson 为例。

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>
    <!-- Jackson for JSON -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.15.2</version>
    </dependency>
</dependencies>

创建上传目录

在你的 Web 项目的 webapp 目录下,创建一个用于存放上传文件的文件夹,webapp/uploads

jQuery文件上传如何与Java后端交互?-图2
(图片来源网络,侵删)

重要: 为了让 Tomcat 等容器能写入这个目录,你需要确保 uploads 文件夹有正确的写入权限,一个更健壮的做法是在服务器启动时,由代码在服务器的文件系统(如 webapps/your-project/WEB-INF/uploads)中创建并管理此目录。


后端 Java 代码 (Servlet)

创建一个 FileUploadServlet.java 文件。

import com.fasterxml.jackson.databind.ObjectMapper;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@WebServlet("/upload")
@MultipartConfig(
    fileSizeThreshold = 1024 * 1024 * 2,    // 2 MB
    maxFileSize = 1024 * 1024 * 10,         // 10 MB
    maxRequestSize = 1024 * 1024 * 15      // 15 MB
)
public class FileUploadServlet extends HttpServlet {
    private static final String UPLOAD_DIR = "uploads"; // 上传目录相对路径
    private final ObjectMapper objectMapper = new ObjectMapper();
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 设置响应内容类型为 JSON
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        Map<String, Object> result = new HashMap<>();
        try {
            // 2. 获取上传的文件 Part
            Part filePart = request.getPart("file"); // "file" 必须与前端 input 的 name 属性一致
            if (filePart == null) {
                result.put("success", false);
                result.put("message", "没有选择文件");
                response.getWriter().write(objectMapper.writeValueAsString(result));
                return;
            }
            // 3. 获取文件名并进行安全处理(防止路径遍历攻击)
            String fileName = getFileName(filePart);
            String applicationPath = getServletContext().getRealPath("");
            String uploadFilePath = applicationPath + File.separator + UPLOAD_DIR;
            // 确保上传目录存在
            File uploadDir = new File(uploadFilePath);
            if (!uploadDir.exists()) {
                uploadDir.mkdir();
            }
            // 4. 构建文件在服务器上的完整路径
            String fullPath = uploadFilePath + File.separator + fileName;
            // 5. 保存文件
            filePart.write(fullPath);
            // 6. 返回成功响应
            result.put("success", true);
            result.put("message", "文件上传成功");
            result.put("fileName", fileName);
            result.put("filePath", UPLOAD_DIR + "/" + fileName); // 返回相对路径,方便前端访问
        } catch (IllegalStateException e) {
            // 文件大小超过限制
            result.put("success", false);
            result.put("message", "文件大小超过限制: " + e.getMessage());
        } catch (Exception e) {
            // 其他错误
            e.printStackTrace();
            result.put("success", false);
            result.put("message", "上传失败: " + e.getMessage());
        }
        // 7. 将结果以 JSON 格式返回给前端
        response.getWriter().write(objectMapper.writeValueAsString(result));
    }
    /**
     * 从 Part 对象中提取文件名,处理不同浏览器的兼容性问题
     */
    private String getFileName(final Part part) {
        final String partHeader = part.getHeader("content-disposition");
        for (String content : partHeader.split(";")) {
            if (content.trim().startsWith("filename")) {
                String fileName = content.substring(content.indexOf('=') + 1).trim().replace("\"", "");
                // 为了防止文件名冲突,可以在这里添加唯一前缀,如 UUID
                // return UUID.randomUUID().toString() + "-" + fileName;
                return fileName;
            }
        }
        return null;
    }
}

代码解释:

  • @WebServlet("/upload"): 定义 Servlet 的访问 URL。
  • @MultipartConfig: 至关重要,这个注解告诉 Servlet 容器这个 Servlet 将处理 multipart/form-data 请求(即文件上传),它还设置了文件大小的限制:
    • fileSizeThreshold: 当文件大小超过此值时,内容会被临时存储到磁盘上,否则存在内存中。
    • maxFileSize: 单个文件的最大允许大小。
    • maxRequestSize: 整个请求(包含所有文件和表单数据)的最大允许大小。
  • request.getPart("file"): 获取名为 file 的文件部分。name 必须和前端的 <input name="file"> 一致。
  • getFileName(): 一个辅助方法,用于从 HTTP 头中安全地提取文件名。
  • filePart.write(fullPath): 将上传的文件内容写入到服务器指定的路径。
  • ObjectMapper: 用于将 Java Map 对象轻松转换为 JSON 字符串发送给前端。

前端 HTML 和 jQuery 代码

创建一个 index.html 文件。

jQuery文件上传如何与Java后端交互?-图3
(图片来源网络,侵删)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">jQuery 文件上传示例</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        .container { max-width: 600px; margin: auto; padding: 20px; border: 1px solid #ccc; border-radius: 5px; }
        .progress { margin-top: 10px; }
        .progress-bar {
            height: 20px;
            background-color: #f0f0f0;
            border-radius: 10px;
            overflow: hidden;
        }
        .progress-bar-fill {
            height: 100%;
            background-color: #5cb85c;
            width: 0%;
            transition: width 0.3s;
        }
        #result { margin-top: 20px; padding: 10px; border-radius: 5px; display: none; }
        .success { background-color: #dff0d8; color: #3c763d; }
        .error { background-color: #f2dede; color: #a94442; }
    </style>
</head>
<body>
    <div class="container">
        <h2>jQuery 文件上传</h2>
        <form id="uploadForm">
            <div>
                <label for="fileInput">选择文件:</label>
                <input type="file" id="fileInput" name="file" required>
            </div>
            <button type="submit">上传文件</button>
        </form>
        <!-- 进度条 -->
        <div class="progress" style="display: none;">
            <div class="progress-bar">
                <div class="progress-bar-fill" id="progressBarFill"></div>
            </div>
        </div>
        <!-- 结果显示区域 -->
        <div id="result"></div>
    </div>
    <!-- 引入 jQuery -->
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script>
        $(document).ready(function() {
            $('#uploadForm').on('submit', function(event) {
                // 1. 阻止表单默认提交行为
                event.preventDefault();
                // 2. 获取表单数据
                var formData = new FormData(this);
                // formData 会自动包含 name="file" 的文件输入框
                // 3. 显示进度条
                $('.progress').show();
                $('#result').hide().removeClass('success error');
                // 4. 发送 AJAX 请求
                $.ajax({
                    url: 'upload', // 对应 Servlet 的 @WebServlet("/upload")
                    type: 'POST',
                    data: formData,
                    // 必须设置为 false,因为 FormData 对象本身会处理 Content-Type
                    processData: false, 
                    // 必须设置为 false,因为 jQuery 默认会为 processData=false 的数据设置 'application/x-www-form-urlencoded'
                    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);
                                $('#progressBarFill').css('width', percent + '%');
                            }
                        }, false);
                        return xhr;
                    },
                    // 请求成功回调
                    success: function(response) {
                        $('.progress').hide();
                        $('#progressBarFill').css('width', '0%');
                        if (response.success) {
                            $('#result').text('上传成功! 文件名: ' + response.fileName)
                                       .addClass('success').show();
                        } else {
                            $('#result').text('上传失败: ' + response.message)
                                       .addClass('error').show();
                        }
                    },
                    // 请求失败回调
                    error: function(jqXHR, textStatus, errorThrown) {
                        $('.progress').hide();
                        $('#progressBarFill').css('width', '0%');
                        var errorMsg = "请求失败: " + textStatus;
                        if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
                            errorMsg = jqXHR.responseJSON.message;
                        }
                        $('#result').text(errorMsg)
                                   .addClass('error').show();
                    }
                });
            });
        });
    </script>
</body>
</html>

前端代码解释:

  • event.preventDefault(): 核心,阻止表单的默认提交,从而实现 AJAX 无刷新上传。
  • new FormData(this): this 指向表单元素。FormData 会自动收集表单中所有 name 属性的字段,包括文件,这是处理文件上传最标准的方式。
  • processData: falsecontentType: false: 必须设置processData: false 阻止 jQuery 将 FormData 对象转换为查询字符串。contentType: false 阻止 jQuery 自动设置 Content-Type 头,让浏览器使用正确的 multipart/form-data 格式和边界(boundary)。
  • xhr.upload.addEventListener('progress', ...): 这是实现上传进度条的关键,我们通过自定义 xhr 对象来监听上传过程中的 progress 事件。
  • successerror 回调: 分别处理服务器返回的成功和失败情况,我们解析返回的 JSON 数据,并更新页面内容给用户反馈。

总结与扩展

你将 FileUploadServlet.classindex.html(以及 uploads 目录)部署到你的服务器上,访问 index.html 就可以测试文件上传了。

常见扩展和最佳实践:

  1. 文件重命名: 为了避免文件名冲突和安全隐患(如恶意文件名),强烈建议在后端保存文件时进行重命名。

    // 在 getFileName 方法中或保存前
    String originalName = getFileName(filePart);
    String fileExtension = originalName.substring(originalName.lastIndexOf("."));
    String newFileName = UUID.randomUUID().toString() + fileExtension;
    String fullPath = uploadFilePath + File.separator + newFileName;
    filePart.write(fullPath);
  2. 文件类型校验:

    • 前端校验: 使用 <input accept=".jpg,.png,.gif"> 可以让浏览器只选择特定类型的文件,但可以被轻易绕过。
    • 后端校验: 必须在后端进行校验,可以通过检查文件名后缀或更可靠地读取文件内容(如 "magic number")来判断。
      // 在 getFileName 方法后添加
      String fileName = getFileName(filePart);
      String fileExtension = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
      List<String> allowedExtensions = Arrays.asList("jpg", "jpeg", "png", "gif");
      if (!allowedExtensions.contains(fileExtension)) {
      result.put("success", false);
      result.put("message", "不支持的文件类型");
      // ... 返回错误
      }
  3. 多文件上传:

    • 前端: 将 <input type="file"> 改为 <input type="file" multiple>
    • 后端: 使用 request.getParts() 获取所有 Part 对象,然后遍历处理。
      // 在 doPost 方法中
      Collection<Part> parts = request.getParts();
      for (Part part : parts) {
      if (part.getName().equals("file")) { // 或者根据你的字段名判断
          // 处理每个文件...
      }
      }
  4. 数据库记录: 上传文件后,你还需要在数据库中记录这个文件的信息(如文件名、路径、上传者、上传时间等),以便后续管理和引用,你可以在 filePart.write(fullPath) 成功后,向数据库插入一条记录。

这个完整的示例为你提供了一个从基础到进阶的 jQuery + Java 文件上传解决方案,涵盖了大多数实际开发中的需求。

分享:
扫描分享到社交APP
上一篇
下一篇