杰瑞科技汇

java jquery 文件上传

核心概念

  1. 前端 (jQuery):

    java jquery 文件上传-图1
    (图片来源网络,侵删)
    • HTML: 创建一个 <input type="file"> 元素让用户选择文件。
    • JavaScript/jQuery: 监听表单提交或按钮点击事件,阻止默认的表单提交行为,然后使用 FormData 对象来收集文件数据。
    • AJAX: 通过 $.ajaxFormData 对象异步发送到后端服务器。
  2. 后端 (Java):

    • Servlet: 接收前端发送的 HTTP 请求,文件数据会以 multipart/form-data 的格式封装在请求体中。
    • Apache Commons FileUpload: 这是一个非常流行且强大的库,专门用于解析 multipart/form-data 请求,从中提取出文件和其他表单字段。
    • 处理: 将接收到的文件保存服务器的指定目录。
    • 响应: 返回一个 JSON 或其他格式的响应,告诉前端上传成功或失败。

第一步:项目环境准备

我们使用一个简单的 Maven Web 项目。

后端依赖 (pom.xml)

你需要添加 Servlet API 和 Apache Commons FileUpload 的依赖。

java jquery 文件上传-图2
(图片来源网络,侵删)
<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>

前端代码关键点解释:

java jquery 文件上传-图3
(图片来源网络,侵删)
  • enctype="multipart/form-data": 必须<form> 标签中设置,否则文件无法正确上传。
  • e.preventDefault(): 阻止表单用传统方式刷新页面,实现 AJAX 无刷新上传。
  • new FormData(): 创建一个表单数据对象,用于封装文件。
  • formData.append('file', file): 将文件添加到 FormData 中,'file' 这个名字需要和后端解析时使用的名字一致。
  • processData: falsecontentType: 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 字符串返回给前端,方便前端处理。

第四步:部署和运行

  1. 将项目打包成 WAR 文件。
  2. 部署到 Tomcat 或其他支持 Servlet 的 Web 服务器中。
  3. 启动服务器。
  4. 在浏览器中访问 http://localhost:8080/你的项目名/index.html
  5. 选择一个文件,点击上传,你将看到进度条变化,并最终收到成功或失败的提示。

安全性与最佳实践

  1. 文件类型验证:

    • 不要仅仅依赖文件扩展名,因为它是可以伪造的。
    • 读取文件的“魔数”(Magic Number),即文件头部的几个字节,来判断真实的文件类型。
    • 在后端,可以维护一个允许上传的白名单(如 jpg, png, pdf, docx),如果文件类型不在白名单中,则直接拒绝。
  2. 文件大小限制:

    • 我们已经在后端设置了 MAX_FILE_SIZE,这是非常重要的防御措施,可以防止用户上传超大文件耗尽服务器磁盘空间或导致 DOS 攻击。
  3. 文件名处理:

    • 我们已经使用了 UUID 来重命名文件,这是一个很好的实践,可以防止恶意文件名(如 ../../../etc/passwd)导致的路径遍历攻击。
  4. 病毒扫描:

    对于生产环境,上传的文件应该经过杀毒软件的扫描,确保文件不包含恶意代码。

  5. 存储位置:

    • 不要将上传的文件直接放在 Web 服务器的根目录下(如 webapps),这样用户可能通过 URL 直接访问到这些文件,除非你明确希望如此,最好是放在一个非 Web 可达的目录,然后通过一个专门的 Servlet 来提供文件下载服务,并在此进行权限控制。

通过以上步骤,你就实现了一个功能完整、带有进度条、并且具备基本安全性的文件上传功能。

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